From 520383a32c4cf76db899aea14e21037b71d4ad0d Mon Sep 17 00:00:00 2001 From: yangmingming Date: Fri, 20 Oct 2023 16:02:01 +0800 Subject: [PATCH 01/15] =?UTF-8?q?=E9=B8=BF=E8=92=99=E7=AD=BE=E5=90=8D?= =?UTF-8?q?=E5=B7=A5=E5=85=B7zip=E4=BB=A3=E7=A0=81=E7=AD=BE=E5=90=8D?= =?UTF-8?q?=EF=BC=8C=E6=9C=AA=E8=81=94=E8=B0=83sdk?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: yangmingming --- .../hap_sign_tool/src/main/resources/help.txt | 1 + .../ohos/hapsigntool/api/model/Options.java | 5 + .../hap/provider/LocalJKSSignProvider.java | 4 +- .../hap/provider/SignProvider.java | 90 ++++++-- .../ohos/hapsigntool/hap/sign/SignHap.java | 198 ++++++++++++------ .../hapsigntool/hap/verify/VerifyHap.java | 49 +++++ .../hapsigntool/hap/verify/VerifyResult.java | 5 + .../com/ohos/hapsigntool/utils/HapUtils.java | 5 + .../hapsigntool/utils/ParamConstants.java | 25 +++ tools/commands.config | 90 +++++++- 10 files changed, 392 insertions(+), 80 deletions(-) diff --git a/hapsigntool/hap_sign_tool/src/main/resources/help.txt b/hapsigntool/hap_sign_tool/src/main/resources/help.txt index 4f293806..735df6a0 100644 --- a/hapsigntool/hap_sign_tool/src/main/resources/help.txt +++ b/hapsigntool/hap_sign_tool/src/main/resources/help.txt @@ -183,6 +183,7 @@ USAGE: [options] -username : user account for online auth, required fields on remoteSign mode with account auth mode; -userPwd : user password for online auth, required fields on remoteSign mode with account auth mode; -ext : extend parameters for remote signer plugin, optional fields; + -codesign : 0 do not code sign, 1 do not code sign, default 1; EXAMPLE: sign-app -mode localSign -keyAlias "oh-app1-key-v1" -appCertFile "D:\OH\app-release-cert.cer" -profileFile "D:\OH\signed-profile.p7b" -inFile "D:\OH\app1-unsigned.hap" -signAlg SHA256withECDSA -keystoreFile "D:\OH\app-keypair.jks" -keystorePwd ****** -outFile "D:\OH\app1-signed.hap -compatibleVersion 8" diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/api/model/Options.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/api/model/Options.java index 855d6677..3b9cd88b 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/api/model/Options.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/api/model/Options.java @@ -235,6 +235,11 @@ public class Options extends HashMap { */ public static final String VALIDITY = "validity"; + /** + * The code sign params of resign hap + */ + public static final String PARAM_CODE_SIGN = "codesign"; + /** * All usages included in the extended key usage. */ diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/provider/LocalJKSSignProvider.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/provider/LocalJKSSignProvider.java index 731abb15..efca0a9a 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/provider/LocalJKSSignProvider.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/provider/LocalJKSSignProvider.java @@ -89,7 +89,7 @@ public class LocalJKSSignProvider extends SignProvider { String[] paramFileds = { ParamConstants.PARAM_LOCAL_JKS_KEYSTORE, ParamConstants.PARAM_LOCAL_JKS_KEYSTORE_CODE, - ParamConstants.PARAM_LOCAL_JKS_KEYALIAS_CODE + ParamConstants.PARAM_LOCAL_JKS_KEYALIAS_CODE, }; Set paramSet = ParamProcessUtil.initParamField(paramFileds); @@ -103,7 +103,7 @@ public class LocalJKSSignProvider extends SignProvider { } } } - + checkCodeSign(); checkPublicKeyPath(); } } diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/provider/SignProvider.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/provider/SignProvider.java index e5da2793..6cdbcc4f 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 @@ -60,6 +60,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.StandardCopyOption; @@ -68,14 +69,7 @@ import java.security.Security; import java.security.cert.CertificateException; import java.security.cert.X509CRL; import java.security.cert.X509Certificate; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.TimeZone; -import java.util.Optional; +import java.util.*; import java.util.jar.JarFile; import java.util.jar.JarOutputStream; @@ -279,8 +273,9 @@ public abstract class SignProvider { checkCompatibleVersion(); File input = new File(signParams.get(ParamConstants.PARAM_BASIC_INPUT_FILE)); output = new File(signParams.get(ParamConstants.PARAM_BASIC_OUTPUT_FILE)); + String suffix = getFileSuffix(output); if (input.getCanonicalPath().equals(output.getCanonicalPath())) { - tmpOutput = File.createTempFile("signedHap", ".hap"); + tmpOutput = File.createTempFile("signedHap", "." + suffix); isPathOverlap = true; } else { tmpOutput = output; @@ -306,6 +301,8 @@ public abstract class SignProvider { signerConfig.setCompatibleVersion(Integer.parseInt( signParams.get(ParamConstants.PARAM_BASIC_COMPATIBLE_VERSION))); ZipDataInput[] contents = {beforeCentralDir, centralDirectory, eocd}; + + appendCodeSignBlock(signerConfig, tmpOutput, suffix, centralDirectoryOffset); byte[] signingBlock = SignHap.sign(contents, signerConfig, optionalBlocks); long newCentralDirectoryOffset = centralDirectoryOffset + signingBlock.length; ZipUtils.setCentralDirectoryOffset(eocdBuffer, newCentralDirectoryOffset); @@ -323,6 +320,54 @@ public abstract class SignProvider { return doAfterSign(isRet, isPathOverlap, tmpOutput, output); } + /** + * append code signBlock + * + * @param signerConfig signerConfig + * @param tmpOutput temp output file + * @param suffix suffix + * @param centralDirectoryOffset central directory offset + */ + private void appendCodeSignBlock(SignerConfig signerConfig, File tmpOutput, + String suffix, long centralDirectoryOffset) { + if (signParams.get(ParamConstants.PARAM_CODE_SIGN) + .equals(ParamConstants.CodeSignFlag.CODE_SIGNED.getCodeSignFlag())){ + // + int codeSignOffset = (int) + (centralDirectoryOffset + ((4 + 4 + 4) * (optionalBlocks.size() + 2) + (4 + 4 + 4))); + byte[] codeSignArray=getCodeSignBlock(tmpOutput,codeSignOffset,suffix); + ByteBuffer result = ByteBuffer.allocate(codeSignArray.length + (4 + 4 + 4)); + result.order(ByteOrder.LITTLE_ENDIAN); + result.putInt(HapUtils.HAP_CODE_SIGN_BLOCK_ID); // type + result.putInt(codeSignArray.length); // length + result.putInt(codeSignOffset); // offset + result.put(codeSignArray); + SigningBlock propertyBlock = new SigningBlock(HapUtils.HAP_PROPERTY_BLOCK_ID, result.array()); + optionalBlocks.add(0,propertyBlock); + } + } + + private byte[] getCodeSignBlock(File tmpOutput, int codeSignOffset, String suffix) { + byte[] byteArray = new byte[256]; + Arrays.fill(byteArray, (byte)'1'); + return byteArray; + } + + /** + * obtain file name suffix + * + * @param output + * @return suffix + * @throws HapFormatException hap format error + */ + private String getFileSuffix(File output) throws HapFormatException { + String[] fileNameArray = output.getName().split("\\."); + if (fileNameArray.length < ParamConstants.FILE_NAME_MIN_LENGTH) { + throw new HapFormatException("hap format error :" + output); + } + return fileNameArray[fileNameArray.length - 1]; + } + /** * Load certificate chain from input parameters * @@ -392,15 +437,15 @@ public abstract class SignProvider { * @param alignment alignment * @throws IOException io error */ - private void copyFileAndAlignment(File input, File tmpOutput, int alignment) throws IOException { + private void copyFileAndAlignment(File input, File tmpOutput, int alignment) + throws IOException, HapFormatException { try (JarFile inputJar = new JarFile(input, false); FileOutputStream outputFile = new FileOutputStream(tmpOutput); JarOutputStream outputJar = new JarOutputStream(outputFile)) { long timestamp = TIMESTAMP; timestamp -= TimeZone.getDefault().getOffset(timestamp); outputJar.setLevel(COMPRESSION_MODE); - List entryNames = SignHap.getEntryNamesFromHap(inputJar); - SignHap.copyFiles(entryNames, inputJar, outputJar, timestamp, alignment); + SignHap.copyFiles(inputJar, outputJar, timestamp, alignment); } } @@ -576,7 +621,8 @@ public abstract class SignProvider { ParamConstants.PARAM_REMOTE_SERVER, ParamConstants.PARAM_BASIC_PROFILE_SIGNED, ParamConstants.PARAM_LOCAL_PUBLIC_CERT, - ParamConstants.PARAM_BASIC_COMPATIBLE_VERSION + ParamConstants.PARAM_BASIC_COMPATIBLE_VERSION, + ParamConstants.PARAM_CODE_SIGN }; Set paramSet = ParamProcessUtil.initParamField(paramFileds); @@ -588,10 +634,28 @@ public abstract class SignProvider { if (!signParams.containsKey(ParamConstants.PARAM_BASIC_PROFILE_SIGNED)) { signParams.put(ParamConstants.PARAM_BASIC_PROFILE_SIGNED, "1"); } + checkCodeSign(); checkSignatureAlg(); checkSignAlignment(); } + /** + * Check code sign, if param do not have code sign default "1". + * + * @throws InvalidParamsException invalid param + */ + protected void checkCodeSign() throws InvalidParamsException { + if (!signParams.containsKey(ParamConstants.PARAM_CODE_SIGN)) { + signParams.put(ParamConstants.PARAM_CODE_SIGN, ParamConstants.CodeSignFlag.CODE_SIGNED.getCodeSignFlag()); + return; + } + String codeSign = signParams.get(ParamConstants.PARAM_CODE_SIGN); + if (!codeSign.equals(ParamConstants.CodeSignFlag.CODE_SIGNED.getCodeSignFlag()) + && !codeSign.equals(ParamConstants.CodeSignFlag.CODE_UNSIGNED.getCodeSignFlag())) { + throw new InvalidParamsException("Invalid parameter: " + ParamConstants.PARAM_CODE_SIGN); + } + } + /** * Check compatible version, if param do not have compatible version default 9. * diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/sign/SignHap.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/sign/SignHap.java index eede0eaa..8f223a1d 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/sign/SignHap.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/sign/SignHap.java @@ -19,8 +19,10 @@ import com.ohos.hapsigntool.api.model.Options; import com.ohos.hapsigntool.hap.config.SignerConfig; import com.ohos.hapsigntool.hap.entity.Pair; import com.ohos.hapsigntool.hap.entity.SigningBlock; +import com.ohos.hapsigntool.hap.exception.HapFormatException; import com.ohos.hapsigntool.hap.exception.SignatureException; import com.ohos.hapsigntool.utils.HapUtils; +import com.ohos.hapsigntool.utils.StringUtils; import com.ohos.hapsigntool.zip.ZipDataInput; import java.io.IOException; @@ -28,17 +30,12 @@ import java.io.InputStream; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.security.DigestException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.jar.JarOutputStream; +import java.util.stream.Collectors; + /** * Hap Signature Scheme signer @@ -63,96 +60,167 @@ public abstract class SignHap { return BLOCK_SIZE; } - /** - * Get all entries' name from hap which is opened as a jar-file. - * - * @param hap input hap-file which is opened as a jar-file. - * @return list of entries' names. - */ - public static List getEntryNamesFromHap(JarFile hap) { - List result = new ArrayList(); - for (Enumeration e = hap.entries(); e.hasMoreElements();) { - JarEntry entry = e.nextElement(); - if (!entry.isDirectory()) { - result.add(entry.getName()); - } - } - return result; - } - /** * Copy the jar file and align the storage entries. * - * @param entryNames list of entries' name * @param in input hap-file which is opened as a jar-file. * @param out output stream of jar. * @param timestamp ZIP file timestamps * @param defaultAlignment default value of alignment. * @throws IOException io error. */ - public static void copyFiles(List entryNames, JarFile in, - JarOutputStream out, long timestamp, int defaultAlignment) throws IOException { - Collections.sort(entryNames); + public static void copyFiles(JarFile in, + JarOutputStream out, long timestamp, int defaultAlignment) throws IOException, HapFormatException { + // split compressed and uncompressed + List entryListStored = in.stream() + .filter(jarFile -> jarFile.getMethod() == JarEntry.STORED).collect(Collectors.toList()); + + // uncompressed special files and place in front + entryListStored = storedEntryListOfSort(entryListStored); long offset = INIT_OFFSET_LEN; - for (String name : entryNames) { - JarEntry inEntry = in.getJarEntry(name); - if (inEntry.getMethod() != JarEntry.STORED) { + String lastAlignmentEntryName = ""; + for (JarEntry inEntry : entryListStored) { + String entryName = inEntry.getName(); + if (!(entryName.endsWith(".so")) && !(entryName.endsWith(".abc"))) { + lastAlignmentEntryName = entryName; + break; + } + } + for (JarEntry inEntry : entryListStored) { + if (inEntry == null) { continue; } offset += JarFile.LOCHDR; - JarEntry outEntry = new JarEntry(inEntry); - outEntry.setTime(timestamp); - - outEntry.setComment(null); - outEntry.setExtra(null); - + JarEntry outEntry = getJarEntry(timestamp, inEntry); offset += outEntry.getName().length(); - int alignment = getStoredEntryDataAlignment(name, defaultAlignment); + int alignment = getStoredEntryDataAlignment(inEntry.getName(), defaultAlignment,lastAlignmentEntryName); if (alignment > 0 && (offset % alignment != 0)) { int needed = alignment - (int) (offset % alignment); outEntry.setExtra(new byte[needed]); offset += needed; } + out.putNextEntry(outEntry); + offset = writeOutputStreamAndGetOffset(in, out, inEntry, offset); + } + List entryListNotStored = in.stream() + .filter(jarFile -> jarFile.getMethod() != JarEntry.STORED).collect(Collectors.toList()); + // process byte alignment of the first compressed file + boolean isAlignmentFlag = StringUtils.isEmpty(lastAlignmentEntryName); + if (isAlignmentFlag) { + if (entryListNotStored.isEmpty()) { + throw new HapFormatException("Hap format is error, file missing"); + } + JarEntry firstEntry = entryListNotStored.get(0); + offset += JarFile.LOCHDR; + JarEntry outEntry = getFirstJarEntry(firstEntry, offset, timestamp); out.putNextEntry(outEntry); byte[] buffer = new byte[BUFFER_LENGTH]; - try (InputStream data = in.getInputStream(inEntry)) { - int num; - while ((num = data.read(buffer)) > 0) { - out.write(buffer, 0, num); - offset += num; - } - out.flush(); + writeOutputStream(in, out, firstEntry, buffer); + } + + copyFilesExceptStoredFile(entryListNotStored, in, out, timestamp, isAlignmentFlag); + } + + /** + * uncompressed special files are placed in front + * + * @param entryListStored stored file entry list + * @return List jarEntryList + */ + private static List storedEntryListOfSort(List entryListStored) { + return entryListStored.stream().sorted((entry1, entry2) -> { + String name1 = entry1.getName(); + String name2 = entry2.getName(); + // files ending with .abc or .so are placed before other files + boolean isSpecial1 = name1.endsWith(".abc") || name1.endsWith(".so"); + boolean isSpecial2 = name2.endsWith(".abc") || name2.endsWith(".so"); + if (isSpecial1 && !isSpecial2) { + return -1; + } else if (!isSpecial1 && isSpecial2) { + return 1; + } else { + // if all files are special files or none of them are special files,the files are sorted lexically + return name1.compareTo(name2); } + }).collect(Collectors.toList()); + } + + private static JarEntry getFirstJarEntry(JarEntry firstEntry, long offset, long timestamp) { + long currentOffset = offset; + JarEntry outEntry = getJarEntry(timestamp, firstEntry); + currentOffset += outEntry.getName().length(); + if (currentOffset % STORED_ENTRY_SO_ALIGNMENT != 0) { + int needed = STORED_ENTRY_SO_ALIGNMENT - (int) (currentOffset % STORED_ENTRY_SO_ALIGNMENT); + outEntry.setExtra(new byte[needed]); } + return outEntry; + } - copyFilesExceptStoredFile(entryNames, in, out, timestamp); + /** + * write first not stored entry to outputStream + * + * @param in jar file + * @param out jarOutputStream + * @param firstEntry jarEntry + * @param buffer byte[] + * @throws IOException IOExpcetion + */ + private static void writeOutputStream(JarFile in, JarOutputStream out, JarEntry firstEntry, byte[] buffer) + throws IOException { + try (InputStream data = in.getInputStream(firstEntry)) { + int num; + while ((num = data.read(buffer)) > 0) { + out.write(buffer, 0, num); + } + out.flush(); + } } - private static void copyFilesExceptStoredFile(List entryNames, JarFile in, - JarOutputStream out, long timestamp) throws IOException { + private static long writeOutputStreamAndGetOffset(JarFile in, JarOutputStream out, JarEntry inEntry, long offset) + throws IOException { byte[] buffer = new byte[BUFFER_LENGTH]; + long currentOffset = offset; + try (InputStream data = in.getInputStream(inEntry)) { + int num; + while ((num = data.read(buffer)) > 0) { + out.write(buffer, 0, num); + currentOffset += num; + } + out.flush(); + } + return currentOffset; + } + + private static JarEntry getJarEntry(long timestamp, JarEntry inEntry) { + JarEntry outEntry = new JarEntry(inEntry); + outEntry.setTime(timestamp); - for (String name : entryNames) { - JarEntry inEntry = in.getJarEntry(name); - if (inEntry.getMethod() == JarEntry.STORED) { + outEntry.setComment(null); + outEntry.setExtra(null); + return outEntry; + } + + private static void copyFilesExceptStoredFile(List entryListNotStored, JarFile in, + JarOutputStream out, long timestamp, boolean isAlignmentFlag) throws IOException { + byte[] buffer = new byte[BUFFER_LENGTH]; + int index=0; + if (isAlignmentFlag){ + index=1; + } + for (;index 0) { - out.write(buffer, 0, num); - } - out.flush(); - } + writeOutputStream(in,out,inEntry,buffer); } } @@ -163,11 +231,15 @@ public abstract class SignHap { * @param defaultAlignment default value of alignment. * @return value of alignment. */ - private static int getStoredEntryDataAlignment(String entryName, int defaultAlignment) { + private static int getStoredEntryDataAlignment(String entryName, int defaultAlignment, + String lastAlignmentEntryName) { if (defaultAlignment <= 0) { return 0; } - if (entryName.endsWith(".so")) { + if (!StringUtils.isEmpty(lastAlignmentEntryName) && entryName.equals(lastAlignmentEntryName)) { + return STORED_ENTRY_SO_ALIGNMENT; + } + if (entryName.endsWith(".so") || entryName.endsWith("abc")) { return STORED_ENTRY_SO_ALIGNMENT; } return defaultAlignment; diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/verify/VerifyHap.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/verify/VerifyHap.java index c5c5834c..ec3bf4e6 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/verify/VerifyHap.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/verify/VerifyHap.java @@ -49,6 +49,8 @@ import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; /** * Class of verify hap. @@ -248,6 +250,11 @@ public class VerifyHap { ByteBuffer signatureSchemeBlock = blockPair.getFirst(); List optionalBlocks = blockPair.getSecond(); Collections.reverse(optionalBlocks); + if(!checkCodeSign(hapFilePath,optionalBlocks)){ + String errMsg="ZIP64 code sign data error"; + return new VerifyResult(false,VerifyResult.RET_CODESIGN_DATA_ERROR,errMsg); + } + long signingBlockOffset = hapSigningBlockAndOffsetInFile.getOffset(); ZipDataInput beforeHapSigningBlock = hapFile.slice(0, signingBlockOffset); ZipDataInput centralDirectoryBlock = hapFile.slice(zipInfo.getCentralDirectoryOffset(), @@ -273,6 +280,48 @@ public class VerifyHap { return result; } + /** + * code sign check + * + * @param hapFilePath hap file path + * @param optionalBlocks optional blocks + * @return true or false + */ + private boolean checkCodeSign(String hapFilePath, List optionalBlocks) { + Map map = optionalBlocks.stream() + .collect(Collectors.toMap(SigningBlock::getType, SigningBlock::getValue)); + byte[] propertyBlockArray = map.get(HapUtils.HAP_PROPERTY_BLOCK_ID); + if (propertyBlockArray!=null&& propertyBlockArray.length>0){ + String[] fileNameArray = hapFilePath.split("\\."); + if (fileNameArray.length> getHapSignatureSchemeBlockAndOptionalBlocks(ByteBuffer hapSigningBlock) throws SignatureNotFoundException { try { diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/verify/VerifyResult.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/verify/VerifyResult.java index 484a23c6..58197ade 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/verify/VerifyResult.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/verify/VerifyResult.java @@ -90,6 +90,11 @@ public class VerifyResult { */ public static final int RET_CRL_ERROR = 10011; + /** + * Return code of file code sign data error. + */ + public static final int RET_CODESIGN_DATA_ERROR = 10012; + private boolean result; private int code; private String message; diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/HapUtils.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/HapUtils.java index e51a3f59..615fd675 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/HapUtils.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/HapUtils.java @@ -73,6 +73,11 @@ public class HapUtils { */ public static final int HAP_PROPERTY_BLOCK_ID = 0x20000003; + /** + * ID of code sign block + */ + public static final int HAP_CODE_SIGN_BLOCK_ID = 0x30000001; + /** * The size of data block used to get digest */ diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/ParamConstants.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/ParamConstants.java index 3b1dbde6..bf066bd7 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/ParamConstants.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/ParamConstants.java @@ -236,6 +236,16 @@ public class ParamConstants { */ public static final String PARAM_RESIGN_CONFIG_FILE = "resignconfig"; + /** + * The code sign params of resign hap + */ + public static final String PARAM_CODE_SIGN = "codesign"; + + /** + * file name split . of min length + */ + public static final int FILE_NAME_MIN_LENGTH = 2; + /** * Enumerated value of whether a profile is signed */ @@ -253,4 +263,19 @@ public class ParamConstants { return signFlag; } } + + public enum CodeSignFlag { + CODE_UNSIGNED("0"), + CODE_SIGNED("1"); + + private String codeSignFlag; + + CodeSignFlag(String codeSignFlag) { + this.codeSignFlag = codeSignFlag; + } + + public String getCodeSignFlag() { + return codeSignFlag; + } + } } \ No newline at end of file diff --git a/tools/commands.config b/tools/commands.config index 559d8c1c..e15ced48 100644 --- a/tools/commands.config +++ b/tools/commands.config @@ -86,10 +86,20 @@ 'verify-profile -inFile "app1-profile1.p7b" -outFile', 'verify-profile -inFile "app1-profile-cert-outTime.p7b" -outFile "result.json"', 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "1"', 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app2.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_nopass.jks" -outFile "app1-signed.hap" -inForm "zip" -profileSigned "1" -extCfgFile "111.txt"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app2.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_nopass.jks" -outFile "app1-signed.hap" -inForm "zip" -profileSigned "1" -extCfgFile "111.txt" -codesign "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app2.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_nopass.jks" -outFile "app1-signed.hap" -inForm "zip" -profileSigned "1" -extCfgFile "111.txt" -codesign "1"', 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "profile.json" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -inForm "bin" ', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "profile.json" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -inForm "bin" -codesign "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "profile.json" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -inForm "bin" -codesign "1"', 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -inForm "bin"', - 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "profile.json" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -profileSigned "0"' + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -inForm "bin" -codesign "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -inForm "bin" -codesign "1"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "profile.json" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -profileSigned "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "profile.json" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -profileSigned "0" -codesign "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "profile.json" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -profileSigned "0" -codesign "1"' ], 'case-assert-false': [ 'generate-keypair -keyPwd 123456 -keyAlg ECC -keySize NIST-P-384 -keystoreFile "ohtest.jks" -keystorePwd 123456 -extCfgFile "111.txt"', @@ -340,44 +350,120 @@ 'verify-profile -inFile "app1-profile1-changed.p7b" -outFile "verify-result.json"', 'verify-profile -inFile "app1-profile1.p7b" -outFile "verify-result.js00on"', 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "remoteSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "remoteSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "remoteSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "1"', 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "1"', 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "1"', 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "rewrw@%$" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "rewrw@%$" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "rewrw@%$" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "1"', 'sign-app -keyAlias "oh-app1-key-v1-222" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v1-222" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "0"', + 'sign-app -keyAlias "oh-app1-key-v1-222" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "1"', 'sign-app -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "0"', + 'sign-app -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "1"', 'sign-app -keyAlias -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -keyAlias -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "0"', + 'sign-app -keyAlias -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "1"', 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456789" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456789" -keystorePwd "123456" -codesign "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456789" -keystorePwd "123456" -codesign "1"', 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keystorePwd "123456" -codesign "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keystorePwd "123456" -codesign "1"', 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "1"', 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "1"', 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile1-changed.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile1-changed.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile1-changed.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "1"', 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "profile.json" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -profileSigned "1"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "profile.json" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -profileSigned "1" -codesign "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "profile.json" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -profileSigned "1" -codesign "1"', 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -profileSigned "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -profileSigned "0" -codesign "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -profileSigned "0" -codesign "1"', 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -profileSigned "5"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -profileSigned "5" -codesign "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -profileSigned "5" -codesign "1"', 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -profileSigned "String"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -profileSigned "String" -codesign "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -profileSigned "String" -codesign "1"', 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "1"', 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "1"', 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "notexist\test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "notexist\test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "notexist\test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "1"', 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "profile.json" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "profile.json" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "profile.json" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "1"', 'sign-app -keyAlias "oh-app1-key-v1" -signAlg -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "1"', 'sign-app -keyAlias "oh-app1-key-v1" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v1" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "1"', 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "HMAC" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "HMAC" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "HMAC" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "1"', 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withRSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withRSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withRSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "1"', 'sign-app -keyAlias "oh-app1-key-v2" -signAlg "SHA256withRSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v2" -signAlg "SHA256withRSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "0"', + 'sign-app -keyAlias "oh-app1-key-v2" -signAlg "SHA256withRSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "1"', 'sign-app -keyAlias "oh-app1-key-v2" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v2" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "0"', + 'sign-app -keyAlias "oh-app1-key-v2" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "1"', 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "1"', 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohnull.p12" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohnull.p12" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohnull.p12" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "1"', 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.txt" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.txt" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.txt" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "1"', 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app2.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_nopass.jks" -outFile "app1-signed.hap" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app2.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_nopass.jks" -outFile "app1-signed.hap" -keystorePwd "123456" -codesign "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app2.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_nopass.jks" -outFile "app1-signed.hap" -keystorePwd "123456" -codesign "1"', 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456789"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456789" -codesign "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456789" -codesign "1"', 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -codesign "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -codesign "1"', 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -keyPwd "123456" -keystorePwd "123456" -codesign "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -keyPwd "123456" -keystorePwd "123456" -codesign "1"', 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile -keyPwd "123456" -keystorePwd "123456" -codesign "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile -keyPwd "123456" -keystorePwd "123456" -codesign "1"', 'sign-profile -keyAlias "oh-profile1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -profileCertFile "profile1_error.pem" -inFile "app1-profile-release.json" -keystoreFile "ohtest_pass.jks" -outFile "app1-profile.p7b" -keyPwd "123456" -keystorePwd "123456"', 'sign-profile -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -profileCertFile "profile1.pem" -inFile "app1-profile-release.json" -keystoreFile "ohtest.jks" -outFile "app1-profile.p7b" -keyPwd "123456" -keystorePwd "123456"', 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1_error.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1_error.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1_error.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "1"', 'sign-app -keyAlias "oh-profile1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456"', + 'sign-app -keyAlias "oh-profile1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "0"', + 'sign-app -keyAlias "oh-profile1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "1"', 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app2.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_nopass.jks" -outFile "app1-signed.hap" -inForm -profileSigned -extCfgFile ', - 'sign-app -keyAlias "oh-app1-key-v1" -keyPwd "123456" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app2.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_nopass.jks" -outFile "app1-signed.hap" -inForm -profileSigned -extCfgFile ' + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app2.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_nopass.jks" -outFile "app1-signed.hap" -inForm -profileSigned -extCfgFile -codesign "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app2.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_nopass.jks" -outFile "app1-signed.hap" -inForm -profileSigned -extCfgFile -codesign "1"', + 'sign-app -keyAlias "oh-app1-key-v1" -keyPwd "123456" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app2.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_nopass.jks" -outFile "app1-signed.hap" -inForm -profileSigned -extCfgFile ', + 'sign-app -keyAlias "oh-app1-key-v1" -keyPwd "123456" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app2.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_nopass.jks" -outFile "app1-signed.hap" -inForm -profileSigned -extCfgFile -codesign "0"', + 'sign-app -keyAlias "oh-app1-key-v1" -keyPwd "123456" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app2.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_nopass.jks" -outFile "app1-signed.hap" -inForm -profileSigned -extCfgFile -codesign "1"' ] } \ No newline at end of file -- Gitee From aee29208b5ad7ee65a8ae1e678e33007f15c0771 Mon Sep 17 00:00:00 2001 From: yangmingming Date: Fri, 20 Oct 2023 16:34:24 +0800 Subject: [PATCH 02/15] =?UTF-8?q?=E9=B8=BF=E8=92=99=E7=AD=BE=E5=90=8D?= =?UTF-8?q?=E5=B7=A5=E5=85=B7zip=E4=BB=A3=E7=A0=81=E7=AD=BE=E5=90=8Dimport?= =?UTF-8?q?=E5=AF=BC=E5=8C=85=E4=B8=8D=E6=8A=98=E5=8F=A0=EF=BC=8Cmd?= =?UTF-8?q?=E6=96=87=E4=BB=B6sign-app=20=E5=A2=9E=E5=8A=A0=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E7=AD=BE=E5=90=8D=E5=8F=82=E6=95=B0=E8=AF=B4=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: yangmingming --- README.md | 3 ++- .../ohos/hapsigntool/hap/provider/SignProvider.java | 13 ++++++++++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 04a052a4..30b99052 100644 --- a/README.md +++ b/README.md @@ -86,7 +86,7 @@ The parameters in the command are described as follows: ```shell -java -jar hap-sign-tool.jar sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "result\app1.pem" -profileFile "result\app1-profile.p7b" -inFile "app1-unsigned.zip" -keystoreFile "result\ohtest.jks" -outFile "result\app1-unsigned.hap" -keyPwd "123456" -keystorePwd "123456" +java -jar hap-sign-tool.jar sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "result\app1.pem" -profileFile "result\app1-profile.p7b" -inFile "app1-unsigned.zip" -keystoreFile "result\ohtest.jks" -outFile "result\app1-unsigned.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "1" ``` The parameters in the command are described as follows: @@ -103,6 +103,7 @@ The parameters in the command are described as follows: ├── -keystoreFile # KeyStore (KS) file, in JKS or P12 format. It is mandatory if the signing mode is localSign. ├── -keystorePwd # KS password. It is optional. ├── -outFile # Signed HAP file to generate. It is mandatory. + ├── -codesign # Signed HAP file to code sign, "0" do not code sign, "1" do not code sign, default "1"; 2. One-click signature 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 6cdbcc4f..e5a09994 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 @@ -23,12 +23,12 @@ import com.ohos.hapsigntool.api.model.Options; import com.ohos.hapsigntool.error.CustomException; import com.ohos.hapsigntool.hap.config.SignerConfig; import com.ohos.hapsigntool.hap.entity.SigningBlock; +import com.ohos.hapsigntool.hap.exception.HapFormatException; import com.ohos.hapsigntool.hap.exception.InvalidParamsException; import com.ohos.hapsigntool.hap.exception.MissingParamsException; import com.ohos.hapsigntool.hap.exception.ProfileException; import com.ohos.hapsigntool.hap.exception.SignatureException; import com.ohos.hapsigntool.hap.exception.VerifyCertificateChainException; -import com.ohos.hapsigntool.hap.exception.HapFormatException; import com.ohos.hapsigntool.hap.sign.SignBin; import com.ohos.hapsigntool.hap.sign.SignHap; import com.ohos.hapsigntool.hap.sign.SignatureAlgorithm; @@ -48,7 +48,6 @@ import com.ohos.hapsigntool.zip.ZipDataInput; import com.ohos.hapsigntool.zip.ZipDataOutput; import com.ohos.hapsigntool.zip.ZipFileInfo; import com.ohos.hapsigntool.zip.ZipUtils; - import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.bouncycastle.cms.CMSException; @@ -69,7 +68,15 @@ import java.security.Security; import java.security.cert.CertificateException; import java.security.cert.X509CRL; import java.security.cert.X509Certificate; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.TimeZone; import java.util.jar.JarFile; import java.util.jar.JarOutputStream; -- Gitee From d6cf4cf8213dd4788f05d7d116abf6ecd6f3d1bf Mon Sep 17 00:00:00 2001 From: zfeixiang Date: Tue, 24 Oct 2023 22:10:01 +0800 Subject: [PATCH 03/15] =?UTF-8?q?=E5=BA=94=E7=94=A8=E5=8F=8A=E4=BA=8C?= =?UTF-8?q?=E8=BF=9B=E5=88=B6=E6=96=87=E4=BB=B6=E4=BB=A3=E7=A0=81=E7=AD=BE?= =?UTF-8?q?=E5=90=8D=E8=83=BD=E5=8A=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: zfeixiang --- .../datastructure/CodeSignBlock.java | 270 +++++++++++ .../datastructure/CodeSignBlockHeader.java | 194 ++++++++ .../datastructure/ElfSignBlock.java | 425 ++++++++++++++++ .../codesigning/datastructure/Extension.java | 76 +++ .../datastructure/FsVerityInfoSegment.java | 155 ++++++ .../datastructure/HapInfoSegment.java | 139 ++++++ .../datastructure/MerkleTreeExtension.java | 144 ++++++ .../datastructure/NativeLibInfoSegment.java | 283 +++++++++++ .../datastructure/SegmentHeader.java | 156 ++++++ .../codesigning/datastructure/SignInfo.java | 453 ++++++++++++++++++ .../datastructure/SignedFilePos.java | 123 +++++ .../exception/CodeSignException.java | 44 ++ .../exception/FsVerityDigestException.java | 33 ++ .../exception/VerifyCodeSignException.java | 45 ++ .../fsverity/FsVerityDescriptor.java | 117 +++++ .../codesigning/fsverity/FsVerityDigest.java | 52 ++ .../fsverity/FsVerityGenerator.java | 158 ++++++ .../fsverity/FsVerityHashAlgorithm.java | 50 ++ .../codesigning/fsverity/MerkleTree.java | 44 ++ .../fsverity/MerkleTreeBuilder.java | 350 ++++++++++++++ .../sign/BcSignedDataGenerator.java | 247 ++++++++++ .../codesigning/sign/CentralDirectory.java | 119 +++++ .../codesigning/sign/CodeSigning.java | 404 ++++++++++++++++ .../codesigning/sign/SignedDataGenerator.java | 41 ++ .../codesigning/sign/VerifyCodeSignature.java | 311 ++++++++++++ .../codesigning/utils/CmsUtils.java | 86 ++++ .../codesigning/utils/DigestUtils.java | 39 ++ .../codesigning/utils/HapUtils.java | 106 ++++ .../codesigning/utils/InputStreamUtils.java | 64 +++ 29 files changed, 4728 insertions(+) create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/CodeSignBlock.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/CodeSignBlockHeader.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/ElfSignBlock.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/Extension.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/FsVerityInfoSegment.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/HapInfoSegment.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/MerkleTreeExtension.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/NativeLibInfoSegment.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/SegmentHeader.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/SignInfo.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/SignedFilePos.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/exception/CodeSignException.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/exception/FsVerityDigestException.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/exception/VerifyCodeSignException.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/fsverity/FsVerityDescriptor.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/fsverity/FsVerityDigest.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/fsverity/FsVerityGenerator.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/fsverity/FsVerityHashAlgorithm.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/fsverity/MerkleTree.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/fsverity/MerkleTreeBuilder.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/sign/BcSignedDataGenerator.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/sign/CentralDirectory.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/sign/CodeSigning.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/sign/SignedDataGenerator.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/sign/VerifyCodeSignature.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/utils/CmsUtils.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/utils/DigestUtils.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/utils/HapUtils.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/utils/InputStreamUtils.java diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/CodeSignBlock.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/CodeSignBlock.java new file mode 100644 index 00000000..50d9719a --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/CodeSignBlock.java @@ -0,0 +1,270 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ohos.hapsigntool.codesigning.datastructure; + +import com.ohos.hapsigntool.codesigning.sign.CodeSigning; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +/** + * code sign block is a chunk of bytes attached to hap package or file. + * It consists of two headers: + * 1) code sign block header + * 2) segment header + * three segments: + * 1) fs-verity info segment + * 2) hap info segment + * 3) so info segment + * one zero padding area in order to align merkle tree raw bytes + * 1) zero padding + * and one area storing merkle tree bytes: + * 1) merkle tree raw bytes + *

+ * After signing a hap, call toByteArray() method to generate a block of bytes + * + * @since 2023/09/08 + */ +public class CodeSignBlock { + /** + * page size in bytes + */ + public static final long PAGE_SIZE_4K = 4096L; + + /** + * Segment header count, including fs-verity info, hap info, so info segment + */ + public static final int SEGMENT_HEADER_COUNT = 3; + + private CodeSignBlockHeader codeSignBlockHeader; + + private final List segmentHeaderList; + + private FsVerityInfoSegment fsVerityInfoSegment; + + private HapInfoSegment hapInfoSegment; + + private NativeLibInfoSegment nativeLibInfoSegment; + + private byte[] zeroPadding; + + private final Map merkleTreeMap; + + public CodeSignBlock() { + this.codeSignBlockHeader = new CodeSignBlockHeader(); + this.segmentHeaderList = new ArrayList<>(); + this.fsVerityInfoSegment = new FsVerityInfoSegment(); + this.hapInfoSegment = new HapInfoSegment(); + this.nativeLibInfoSegment = new NativeLibInfoSegment(); + this.merkleTreeMap = new HashMap<>(); + } + + /** + * Add one merkle tree into merkleTreeMap + * + * @param key file name + * @param merkleTree merkle tree raw bytes + */ + public void addOneMerkleTree(String key, byte[] merkleTree) { + if (merkleTree == null) { + this.merkleTreeMap.put(key, new byte[0]); + } else { + this.merkleTreeMap.put(key, merkleTree); + } + } + + /** + * Get one merkle tree from merkleTreeMap by file name + * + * @param key file name + * @return merkle tree bytes + */ + public byte[] getOneMerkleTreeByFileName(String key) { + return this.merkleTreeMap.get(key); + } + + /** + * set code sign block flag + */ + public void setCodeSignBlockFlag() { + int flags = CodeSignBlockHeader.FLAG_MERKLE_TREE_INLINED; + if (this.nativeLibInfoSegment.getSectionNum() != 0) { + flags += CodeSignBlockHeader.FLAG_NATIVE_LIB_INCLUDED; + } + this.codeSignBlockHeader.setFlags(flags); + } + + /** + * set segmentNum defined in code sign block header, equals length if segmentHeaderList + */ + public void setSegmentNum() { + this.codeSignBlockHeader.setSegmentNum(segmentHeaderList.size()); + } + + /** + * add one segment to segmentHeaderList + * + * @param sh segment header + */ + public void addToSegmentList(SegmentHeader sh) { + this.segmentHeaderList.add(sh); + } + + public List getSegmentHeaderList() { + return segmentHeaderList; + } + + /** + * set segment header list + */ + public void setSegmentHeaders() { + // fs-verity info segment + segmentHeaderList.add(new SegmentHeader(SegmentHeader.CSB_FSVERITY_INFO_SEG, this.fsVerityInfoSegment.size())); + // hap info segment + segmentHeaderList.add(new SegmentHeader(SegmentHeader.CSB_HAP_META_SEG, this.hapInfoSegment.size())); + // native lib info segment + segmentHeaderList.add( + new SegmentHeader(SegmentHeader.CSB_NATIVE_LIB_INFO_SEG, this.nativeLibInfoSegment.size())); + } + + public CodeSignBlockHeader getCodeSignBlockHeader() { + return codeSignBlockHeader; + } + + public void setCodeSignBlockHeader(CodeSignBlockHeader csbHeader) { + this.codeSignBlockHeader = csbHeader; + } + + public void setFsVerityInfoSegment(FsVerityInfoSegment fsVeritySeg) { + this.fsVerityInfoSegment = fsVeritySeg; + } + + public FsVerityInfoSegment getFsVerityInfoSegment() { + return fsVerityInfoSegment; + } + + public HapInfoSegment getHapInfoSegment() { + return hapInfoSegment; + } + + public void setHapInfoSegment(HapInfoSegment hapSeg) { + this.hapInfoSegment = hapSeg; + } + + public NativeLibInfoSegment getSoInfoSegment() { + return nativeLibInfoSegment; + } + + public void setSoInfoSegment(NativeLibInfoSegment soSeg) { + this.nativeLibInfoSegment = soSeg; + } + + /** + * Convert code sign block object to a newly created byte array + * + * @return Byte array representation of a CodeSignBlock object + */ + public byte[] toByteArray() { + ByteBuffer bf = ByteBuffer.allocate(this.codeSignBlockHeader.getBlockSize()).order(ByteOrder.LITTLE_ENDIAN); + bf.put(this.codeSignBlockHeader.toByteArray()); + for (SegmentHeader sh : this.segmentHeaderList) { + bf.put(sh.toByteArray()); + } + bf.put(this.zeroPadding); + // Hap merkle tree + if (this.hapInfoSegment.getSignInfo().getExtensionByType(MerkleTreeExtension.MERKLE_TREE_INLINED) != null) { + bf.put(merkleTreeMap.get("Hap")); + } + bf.put(this.fsVerityInfoSegment.toByteArray()); + bf.put(this.hapInfoSegment.toByteArray()); + bf.put(this.nativeLibInfoSegment.toByteArray()); + return bf.array(); + } + + /** + * SegmentOffset is the position of each segment defined in segmentHeaderList, + * based on the start position of code sign block + */ + public void computeSegmentOffset() { + // 1) the first segment is placed after merkle tree + int segmentOffset = CodeSignBlockHeader.size() + + this.segmentHeaderList.size() * SegmentHeader.SEGMENT_HEADER_LENGTH + this.zeroPadding.length + + this.getOneMerkleTreeByFileName(CodeSigning.HAP_SIGNATURE_ENTRY_NAME).length; + for (SegmentHeader sh : segmentHeaderList) { + sh.setSegmentOffset(segmentOffset); + segmentOffset += sh.getSegmentSize(); + } + } + + /** + * Compute the offset to store merkle tree raw bytes based on file start + * + * @param codeSignBlockOffset offset to store code sign block based on file start + * @return offset to store merkle tree based on the file start, it includes the codeSignBlockOffset + */ + public long computeMerkleTreeOffset(long codeSignBlockOffset) { + long sizeWithoutMerkleTree = CodeSignBlockHeader.size() + + SEGMENT_HEADER_COUNT * SegmentHeader.SEGMENT_HEADER_LENGTH; + // add code sign block offset while computing align position for merkle tree + long residual = (codeSignBlockOffset + sizeWithoutMerkleTree) % PAGE_SIZE_4K; + if (residual == 0) { + this.zeroPadding = new byte[0]; + } else { + this.zeroPadding = new byte[(int) (PAGE_SIZE_4K - residual)]; + } + return codeSignBlockOffset + sizeWithoutMerkleTree + zeroPadding.length; + } + + /** + * Convert CodeSignBlock to bytes + * + * @param fsvTreeOffset merkle tree offset + * @return byte array representing the code sign block + */ + public byte[] generateCodeSignBlockByte(long fsvTreeOffset) { + // 1) compute overall block size without merkle tree + long csbSize = CodeSignBlockHeader.size() + + (long) this.segmentHeaderList.size() * SegmentHeader.SEGMENT_HEADER_LENGTH + this.zeroPadding.length + + this.getOneMerkleTreeByFileName(CodeSigning.HAP_SIGNATURE_ENTRY_NAME).length + + this.fsVerityInfoSegment.size() + this.hapInfoSegment.size() + this.nativeLibInfoSegment.size(); + Extension ext = this.hapInfoSegment.getSignInfo().getExtensionByType(MerkleTreeExtension.MERKLE_TREE_INLINED); + if (ext instanceof MerkleTreeExtension) { + MerkleTreeExtension merkleTreeExtension = (MerkleTreeExtension) ext; + merkleTreeExtension.setMerkleTreeOffset(fsvTreeOffset); + } + this.codeSignBlockHeader.setBlockSize(csbSize); + // 2) generate byte array of complete code sign block + return toByteArray(); + } + + /** + * Return a string representation of the object + * + * @return string representation of the object + */ + public String toString() { + return String.format(Locale.ROOT, + "CodeSignBlockHeader[%s], SegmentHeaders[%s], FsVeritySeg[%s], HapInfoSeg[%s], SoInfoSeg[%s]", + this.codeSignBlockHeader, Arrays.toString(this.segmentHeaderList.toArray()), this.fsVerityInfoSegment, + this.hapInfoSegment, this.nativeLibInfoSegment); + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/CodeSignBlockHeader.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/CodeSignBlockHeader.java new file mode 100644 index 00000000..fdb9554b --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/CodeSignBlockHeader.java @@ -0,0 +1,194 @@ +/* + * Copyright (c) 2022 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. + * + * @since 2023/09/08 + */ + +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. + */ +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 = 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]; + + /** + * Default construct of CodeSignBlockHeader + */ + public CodeSignBlockHeader() { + } + + /** + * Construct of CodeSignBlockHeader + * + * @param magic magic number + * @param version version + * @param blockSize code sign block size + * @param segmentNum segment number + * @param flags flags + * @param reserved reserved + */ + public CodeSignBlockHeader(long magic, int version, int blockSize, int segmentNum, int flags, byte[] reserved) { + this.magic = magic; + this.version = version; + this.blockSize = blockSize; + this.segmentNum = segmentNum; + this.flags = flags; + this.reserved = 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 segmentNum of CodeSignBlockHeader"); + } + byte[] inReserved = new byte[RESERVED_BYTE_ARRAY_LENGTH]; + bf.get(inReserved); + return new CodeSignBlockHeader(inMagic, inVersion, inBlockSize, inSegmentNum, inFlags, inReserved); + } + + /** + * 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); + } +} 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 00000000..0a2a18a9 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/ElfSignBlock.java @@ -0,0 +1,425 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ohos.hapsigntool.codesigning.datastructure; + +import com.ohos.hapsigntool.codesigning.exception.VerifyCodeSignException; +import com.ohos.hapsigntool.codesigning.fsverity.FsVerityDescriptor; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Arrays; + +/** + * elf sign block is a chunk of bytes attached to elf file. + * 1) u32 type: 0x2 merkle tree + * 2) u32 length: merkle tree with padding size + * 3) u8[] merkle tree data + * 4) u32 type: 0x1 fsverity descriptor + * 5) u32 length: fsverity descriptor size + * 6) u8 version: fs-verity version + * 7) u8 hashAlgorithm: hash algorithm to use for the Merkle tree + * 8) u8 log2BlockSize: log2 of size of data and tree blocks + * 9) u8 saltSize: byte size of salt + * 10) u32 signSize: byte size of signature + * 11) u64 dataSize: byte size of data being signed + * 12) u8[64] rootHash: merkle tree root hash + * 13) u8[32] salt: salt used in signing + * 14) u32 flags + * 15) u32 reserved + * 16) u64 treeOffset: merkle tree offset + * 17) u8[128] reserved + * 18) u8[] signature: signature after signing the data in byte array representation + * + * @since 2023/09/08 + */ +public class ElfSignBlock { + public static final int PAGE_SIZE_4k = 4096; + + /** + * Type of MerkleTree + */ + public static final int MERKLE_TREE_INLINED = 0x2; + + public static final int ROOT_HASH_SIZE = 64; + + public static final int SALT_BUFFER_LENGTH = 32; + + private static final int RESERVED_BYTE_ARRAY_LENGTH = 128; + + public static final int FSD_WITHOUT_SIGN_LENGTH = 256; + + private int treeType; + + private int treeLength; + + // zero padding before merkle tree + private byte[] paddingBeforeTree; + + private byte[] merkleTreeData; + + private int fsdType; + + private int fsdLength; + + private byte fsVersion; + + private byte fsHashAlgorithm; + + private byte log2BlockSize; + + private byte saltSize; + + private int signSize; + + private long dataSize; + + private byte[] rootHash; + + private byte[] salt; + + private int flags; + + private int reservedInt; + + private long treeOffset; + + private byte[] reservedData; + + private byte[] signature; + + private ElfSignBlock(ElfSignBlockBuilder builder) { + this.treeType = builder.treeType; + this.treeLength = builder.treeLength; + this.paddingBeforeTree = builder.paddingBeforeTree; + this.merkleTreeData = builder.merkleTreeData; + this.fsdType = builder.fsdType; + this.fsdLength = builder.fsdLength; + this.fsVersion = builder.fsVersion; + this.fsHashAlgorithm = builder.fsHashAlgorithm; + this.log2BlockSize = builder.log2BlockSize; + this.saltSize = builder.saltSize; + this.signSize = builder.signSize; + this.dataSize = builder.dataSize; + this.rootHash = builder.rootHash; + this.salt = builder.salt; + this.flags = builder.flags; + this.reservedInt = builder.reservedInt; + this.treeOffset = builder.treeOffset; + this.reservedData = builder.reservedData; + this.signature = builder.signature; + } + + /** + * Return the byte size of code sign block + * + * @return byte size of code sign block + */ + public int size() { + return Integer.BYTES * 2 + paddingBeforeTree.length + merkleTreeData.length + Integer.BYTES * 2 + + FSD_WITHOUT_SIGN_LENGTH + signature.length; + } + + public void addFsVerityInfo(byte version, byte hashAlgorithm, byte log2BlockSize) { + this.fsVersion = version; + this.fsHashAlgorithm = hashAlgorithm; + this.log2BlockSize = log2BlockSize; + } + + public void addSignInfo(int saltSize, int flags, long fileSize, byte[] salt, byte[] signature) { + this.saltSize = (byte) saltSize; + this.flags = flags; + this.dataSize = fileSize; + if (salt != null) { + this.salt = Arrays.copyOf(salt, SALT_BUFFER_LENGTH); + } + if (signature != null) { + this.signature = signature; + } + this.signSize = this.signature.length; + this.fsdLength = FSD_WITHOUT_SIGN_LENGTH + signSize; + } + + public void addMerkleTreeInfo(byte[] merkleTreeBytes, long fsvTreeOffset, byte[] rootHash) { + if (merkleTreeBytes != null) { + this.merkleTreeData = merkleTreeBytes; + } + this.treeOffset = fsvTreeOffset; + if (rootHash != null) { + this.rootHash = Arrays.copyOf(rootHash, ROOT_HASH_SIZE); + } + treeLength = paddingBeforeTree.length + merkleTreeData.length; + } + + /** + * add padding length offset while computing align position for merkle tree + * + * @param signBlockOffset sign block offset based on the start of file + * @return merkle tree raw bytes offset based on the start of file + */ + public long computeMerkleTreeOffset(long signBlockOffset) { + long residual = (signBlockOffset + Integer.BYTES * 2) % PAGE_SIZE_4k; + if (residual > 0) { + this.paddingBeforeTree = new byte[(int) (PAGE_SIZE_4k - residual)]; + } + return signBlockOffset + Integer.BYTES * 2 + paddingBeforeTree.length; + } + + public byte[] getMerkleTreeData() { + return merkleTreeData; + } + + public long getDataSize() { + return dataSize; + } + + public long getTreeOffset() { + return treeOffset; + } + + public byte[] getSignature() { + return signature; + } + + /** + * Return a string representation of the object + * + * @return string representation of the object + */ + public byte[] toByteArray() { + ByteBuffer bf = ByteBuffer.allocate(size()).order(ByteOrder.LITTLE_ENDIAN); + bf.putInt(treeType); + bf.putInt(paddingBeforeTree.length + merkleTreeData.length); + bf.put(paddingBeforeTree); + bf.put(merkleTreeData); + bf.putInt(fsdType); + bf.putInt(fsdLength); + bf.put(fsVersion); + bf.put(fsHashAlgorithm); + bf.put(log2BlockSize); + bf.put(saltSize); + bf.putInt(signSize); + bf.putLong(dataSize); + bf.put(rootHash); + bf.put(salt); + bf.putInt(flags); + bf.putInt(reservedInt); + bf.putLong(treeOffset); + bf.put(reservedData); + bf.put(signature); + return bf.array(); + } + + public static ElfSignBlock fromByteArray(byte[] bytes, long signBlockOffset) throws VerifyCodeSignException { + ByteBuffer bf = ByteBuffer.allocate(bytes.length).order(ByteOrder.LITTLE_ENDIAN); + bf.put(bytes); + // after put, rewind is mandatory before get + bf.rewind(); + ElfSignBlockBuilder builder = new ElfSignBlockBuilder(); + int inTreeType = bf.getInt(); + if (MERKLE_TREE_INLINED != inTreeType) { + throw new VerifyCodeSignException("Invalid merkle tree type of ElfSignBlock"); + } + int inTreeLength = bf.getInt(); + builder.setTreeType(inTreeType).setTreeLength(inTreeLength); + int paddingLength = (int) (PAGE_SIZE_4k - (signBlockOffset + Integer.BYTES * 2) % PAGE_SIZE_4k); + byte[] zeroPadding = new byte[paddingLength]; + bf.get(zeroPadding); + byte[] treeWithoutPadding = new byte[inTreeLength - paddingLength]; + bf.get(treeWithoutPadding); + builder.setPaddingBeforeTree(zeroPadding).setMerkleTreeData(treeWithoutPadding); + int inFsdType = bf.getInt(); + if (FsVerityDescriptor.FS_VERITY_DESCRIPTOR_TYPE != inFsdType) { + throw new VerifyCodeSignException("Invalid fs-verify descriptor type of ElfSignBlock"); + } + int inFsdLength = bf.getInt(); + if (bytes.length != Integer.BYTES * 2 + inTreeLength + Integer.BYTES * 2 + inFsdLength) { + throw new VerifyCodeSignException("Invalid fs-verify descriptor with signature length of ElfSignBlock"); + } + builder.setFsdType(inFsdType).setFsdLength(inFsdLength); + byte inFsVersion = bf.get(); + if (FsVerityDescriptor.VERSION != inFsVersion) { + throw new VerifyCodeSignException("Invalid fs-verify descriptor version of ElfSignBlock"); + } + byte inFsHashAlgorithm = bf.get(); + byte inLog2BlockSize = bf.get(); + builder.setFsVersion(inFsVersion).setFsHashAlgorithm(inFsHashAlgorithm).setLog2BlockSize(inLog2BlockSize); + byte inSaltSize = bf.get(); + if (SALT_BUFFER_LENGTH != inSaltSize) { + throw new VerifyCodeSignException("Invalid salt size of ElfSignBlock"); + } + int inSignSize = bf.getInt(); + if (inFsdLength != inSignSize + FSD_WITHOUT_SIGN_LENGTH) { + throw new VerifyCodeSignException("Invalid sign size of ElfSignBlock"); + } + int inDataSize = bf.getInt(); + byte[] inRootHash = new byte[ROOT_HASH_SIZE]; + bf.get(inRootHash); + builder.setSaltSize(inSaltSize).setSignSize(inSignSize).setDataSize(inDataSize).setRootHash(inRootHash); + byte[] inSalt = new byte[inSaltSize]; + bf.get(inSalt); + int inFlags = bf.getInt(); + bf.getInt(); + long inTreeOffset = bf.getLong(); + if (inTreeOffset % PAGE_SIZE_4k != 0) { + throw new VerifyCodeSignException("Invalid merkle tree offset of ElfSignBlock"); + } + bf.get(new byte[RESERVED_BYTE_ARRAY_LENGTH]); + byte[] inSignature = new byte[inSignSize]; + bf.get(inSignature); + builder.setSalt(inSalt).setFlags(inFlags).setTreeOffset(inTreeOffset).setSignature(inSignature); + return builder.build(); + } + + public static class ElfSignBlockBuilder { + private int treeType = MERKLE_TREE_INLINED; + + private int treeLength; + + // zero padding before merkle tree + private byte[] paddingBeforeTree = new byte[0]; + + private byte[] merkleTreeData = new byte[0]; + + private int fsdType = FsVerityDescriptor.FS_VERITY_DESCRIPTOR_TYPE; + + private int fsdLength = FSD_WITHOUT_SIGN_LENGTH; + + private byte fsVersion = FsVerityDescriptor.VERSION; + + private byte fsHashAlgorithm; + + private byte log2BlockSize; + + private byte saltSize; + + private int signSize; + + private long dataSize; + + private byte[] rootHash = new byte[ROOT_HASH_SIZE]; + + private byte[] salt = new byte[SALT_BUFFER_LENGTH]; + + private int flags; + + private int reservedInt = 0; + + private long treeOffset; + + private byte[] reservedData = new byte[RESERVED_BYTE_ARRAY_LENGTH]; + + private byte[] signature = new byte[0]; + + public ElfSignBlockBuilder setTreeType(int treeType) { + this.treeType = treeType; + return this; + } + + public ElfSignBlockBuilder setTreeLength(int treeLength) { + this.treeLength = treeLength; + return this; + } + + public ElfSignBlockBuilder setPaddingBeforeTree(byte[] paddingBeforeTree) { + this.paddingBeforeTree = paddingBeforeTree; + return this; + } + + public ElfSignBlockBuilder setMerkleTreeData(byte[] merkleTreeData) { + this.merkleTreeData = merkleTreeData; + return this; + } + + public ElfSignBlockBuilder setFsdType(int fsdType) { + this.fsdType = fsdType; + return this; + } + + public ElfSignBlockBuilder setFsdLength(int fsdLength) { + this.fsdLength = fsdLength; + return this; + } + + public ElfSignBlockBuilder setFsVersion(byte fsVersion) { + this.fsVersion = fsVersion; + return this; + } + + public ElfSignBlockBuilder setFsHashAlgorithm(byte fsHashAlgorithm) { + this.fsHashAlgorithm = fsHashAlgorithm; + return this; + } + + public ElfSignBlockBuilder setLog2BlockSize(byte log2BlockSize) { + this.log2BlockSize = log2BlockSize; + return this; + } + + public ElfSignBlockBuilder setSaltSize(byte saltSize) { + this.saltSize = saltSize; + return this; + } + + public ElfSignBlockBuilder setSignSize(int signSize) { + this.signSize = signSize; + return this; + } + + public ElfSignBlockBuilder setDataSize(long dataSize) { + this.dataSize = dataSize; + return this; + } + + public ElfSignBlockBuilder setRootHash(byte[] rootHash) { + this.rootHash = rootHash; + return this; + } + + public ElfSignBlockBuilder setSalt(byte[] salt) { + this.salt = salt; + return this; + } + + public ElfSignBlockBuilder setFlags(int flags) { + this.flags = flags; + return this; + } + + public ElfSignBlockBuilder setReservedInt(int reservedInt) { + this.reservedInt = reservedInt; + return this; + } + + public ElfSignBlockBuilder setTreeOffset(long treeOffset) { + this.treeOffset = treeOffset; + return this; + } + + public ElfSignBlockBuilder setReservedData(byte[] reservedData) { + this.reservedData = reservedData; + return this; + } + + public ElfSignBlockBuilder setSignature(byte[] signature) { + this.signature = signature; + return this; + } + + public ElfSignBlock build() { + return new ElfSignBlock(this); + } + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/Extension.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/Extension.java new file mode 100644 index 00000000..538ba380 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/Extension.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2022 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 00000000..8043f07a --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/FsVerityInfoSegment.java @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2022 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 00000000..8106a016 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/HapInfoSegment.java @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2022 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 00000000..e6eb88e0 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/MerkleTreeExtension.java @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2022 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 multiple of 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 00000000..49edd4d7 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/NativeLibInfoSegment.java @@ -0,0 +1,283 @@ +/* + * Copyright (c) 2022 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 = MAGIC_NUM; + + private int segmentSize; + + private int sectionNum; + + private List> soInfoList = new ArrayList<>(); + + private List signedFilePosList = new ArrayList<>(); + + private List fileNameList = new ArrayList<>(); + + private List signInfoList = new ArrayList<>(); + + private byte[] zeroPadding = new byte[0]; + + private int fileNameListBlockSize; + + private int signInfoListBlockSize; + + /** + * Default constructor + */ + public NativeLibInfoSegment() { + } + + /** + * Constructor for SoInfoSegment by byte array + * + * @param magic byte array representation of SoInfoSegment object + * @param segmentSize byte size of NativeLibInfoSegment + * @param sectionNum number of native libs + * @param signedFilePosList offset and size of each file + * @param fileNameList file name of each signed file + * @param signInfoList sign info of each signed file + * @param zeroPadding zero padding before sign info list + */ + public NativeLibInfoSegment(int magic, int segmentSize, int sectionNum, List signedFilePosList, + List fileNameList, List signInfoList, byte[] zeroPadding) { + this.magic = magic; + this.segmentSize = segmentSize; + this.sectionNum = sectionNum; + this.signedFilePosList = signedFilePosList; + this.fileNameList = fileNameList; + this.signInfoList = signInfoList; + this.zeroPadding = zeroPadding; + } + + /** + * set soInfoList, generate fileNameList and signInfoList + * + * @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 += signInfoList.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 NativeLibInfoSegment(inMagic, inSegmentSize, inSectionNum, inSignedFilePosList, inFileNameList, + inSignInfoList, inZeroPadding); + } + + /** + * 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], signFileEntryList[%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())); + } +} 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 00000000..878b7529 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/SegmentHeader.java @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2022 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 segmentSize byte size of the segment + * @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 lager 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 00000000..93d0ab49 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/SignInfo.java @@ -0,0 +1,453 @@ +/* + * Copyright (c) 2022 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 + */ + public 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 00000000..59163d71 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/SignedFilePos.java @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2022 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 00000000..8a89924c --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/exception/CodeSignException.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2022 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 00000000..309c09d0 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/exception/FsVerityDigestException.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2022 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 00000000..ad9dbc95 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/exception/VerifyCodeSignException.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2022 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 00000000..fd8693db --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/fsverity/FsVerityDescriptor.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ohos.hapsigntool.codesigning.fsverity; + +import com.ohos.hapsigntool.codesigning.exception.FsVerityDigestException; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * Format of FsVerity descriptor + * uint8 verision + * uint8 hashAlgorithm + * uint8 log2BlockSize + * uint8 saltSize + * uint8[4] 0 + * le64 dataSize + * uint8[64] rootHash + * uint8[32] salt + * uint32 flags + * uint8[4] 0 + * uint64 treeOffset + * uint8[128] 0 + * + * @since 2023/06/05 + */ +public class FsVerityDescriptor { + /** + * fs-verify version, must be 1 + */ + public static final byte VERSION = 1; + + /** + * Indicating merkle tree offset is set in fs-verify descriptor + */ + public static final int FLAG_STORE_MERKLE_TREE_OFFSET = 0x1; + + public static final int FS_VERITY_DESCRIPTOR_TYPE = 0x1; + + private static final int DESCRIPTOR_SIZE = 256; + + private static final int ROOT_HASH_FILED_SIZE = 64; + + private static final int SALT_SIZE = 32; + + private static final int FIRST_RESERVED_SIZE = 4; + + private static final int RESERVED_SIZE_AFTER_FLAGS = 4; + + /** + * Get FsVerity descriptor + * + * @param fileSize size of input + * @param hashAlgorithm hash algorithm id + * @param log2BlockSize log2 of hash block size + * @param salt salt used for hash + * @param rawRootHash root hash of merkle tree + * @param flags flag indicating whether merkle tree offset is present in fs-verify descriptor + * @param merkleTreeOffset merkle tree offset based on file start + * @return bytes of descriptor + * @throws FsVerityDigestException if error + */ + public static byte[] getDescriptor(long fileSize, byte hashAlgorithm, byte log2BlockSize, byte[] salt, + byte[] rawRootHash, int flags, long merkleTreeOffset) throws FsVerityDigestException { + ByteBuffer buffer = ByteBuffer.allocate(DESCRIPTOR_SIZE).order(ByteOrder.LITTLE_ENDIAN); + buffer.put(VERSION); + buffer.put(hashAlgorithm); + buffer.put(log2BlockSize); + if (salt == null) { + buffer.put((byte) 0); + } else if (salt.length > SALT_SIZE) { + throw new FsVerityDigestException("Salt is too long"); + } else { + buffer.put((byte) salt.length); + } + writeBytesWithSize(buffer, null, FIRST_RESERVED_SIZE); + buffer.putLong(fileSize); + writeBytesWithSize(buffer, rawRootHash, ROOT_HASH_FILED_SIZE); + writeBytesWithSize(buffer, salt, SALT_SIZE); + buffer.putInt(flags); + writeBytesWithSize(buffer, null, RESERVED_SIZE_AFTER_FLAGS); + buffer.putLong(merkleTreeOffset); + return buffer.array(); + } + + /** + * Write bytes to ByteBuffer with specific size + * + * @param buffer target buffer + * @param src bytes to write + * @param size size of written bytes, fill 0 if src bytes is long enough + */ + private static void writeBytesWithSize(ByteBuffer buffer, byte[] src, int size) { + int pos = buffer.position(); + if (src != null) { + if (src.length > size) { + buffer.put(src, 0, size); + } else { + buffer.put(src); + } + } + buffer.position(pos + size); + } +} diff --git a/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 00000000..6fafdaaa --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/fsverity/FsVerityDigest.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2022 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 00000000..ef2008b3 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/fsverity/FsVerityGenerator.java @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ohos.hapsigntool.codesigning.fsverity; + +import com.ohos.hapsigntool.codesigning.exception.FsVerityDigestException; +import com.ohos.hapsigntool.codesigning.utils.DigestUtils; + +import java.io.IOException; +import java.io.InputStream; +import java.security.NoSuchAlgorithmException; + +/** + * FsVerity data generator supper class + * + * @since 2023/06/05 + */ +public class FsVerityGenerator { + /** + * FsVerity hash algorithm + */ + private static final FsVerityHashAlgorithm FS_VERITY_HASH_ALGORITHM = FsVerityHashAlgorithm.SHA256; + + private static final byte LOG_2_OF_FSVERITY_HASH_PAGE_SIZE = 12; + + /** + * salt for hashing one page + */ + protected byte[] salt = null; + + private byte[] fsVerityDigest = null; + + private byte[] treeBytes = null; + + private byte[] rootHash = null; + + /** + * generate merkle tree of given input + * + * @param inputStream input stream for generate merkle tree + * @param size total size of input stream + * @param fsVerityHashAlgorithm hash algorithm for FsVerity + * @return merkle tree + * @throws FsVerityDigestException if error + */ + public MerkleTree generateMerkleTree(InputStream inputStream, long size, + FsVerityHashAlgorithm fsVerityHashAlgorithm) throws FsVerityDigestException { + MerkleTree merkleTree; + try (MerkleTreeBuilder builder = new MerkleTreeBuilder()) { + merkleTree = builder.generateMerkleTree(inputStream, size, fsVerityHashAlgorithm); + } catch (IOException e) { + throw new FsVerityDigestException("IOException: " + e.getMessage()); + } catch (NoSuchAlgorithmException e) { + throw new FsVerityDigestException("Invalid algorithm:" + e.getMessage()); + } + return merkleTree; + } + + /** + * generate FsVerity digest of given input + * + * @param inputStream input stream for generate FsVerity digest + * @param size total size of input stream + * @param fsvTreeOffset merkle tree raw bytes offset based on the start of file + * @throws FsVerityDigestException if error + */ + public void generateFsVerityDigest(InputStream inputStream, long size, long fsvTreeOffset) + throws FsVerityDigestException { + MerkleTree merkleTree; + if (size == 0) { + merkleTree = new MerkleTree(null, null, FS_VERITY_HASH_ALGORITHM); + } else { + merkleTree = generateMerkleTree(inputStream, size, FS_VERITY_HASH_ALGORITHM); + } + int flags = fsvTreeOffset == 0 ? 0 : FsVerityDescriptor.FLAG_STORE_MERKLE_TREE_OFFSET; + byte[] fsVerityDescriptor = FsVerityDescriptor.getDescriptor(size, FS_VERITY_HASH_ALGORITHM.getId(), + LOG_2_OF_FSVERITY_HASH_PAGE_SIZE, salt, merkleTree.rootHash, flags, fsvTreeOffset); + 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 00000000..3948d895 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/fsverity/FsVerityHashAlgorithm.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2022 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 00000000..1c5d3b51 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/fsverity/MerkleTree.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2022 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 00000000..b8d67576 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/fsverity/MerkleTreeBuilder.java @@ -0,0 +1,350 @@ +/* + * Copyright (c) 2022 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.*; + +/** + * 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 = 0; + 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 count + */ + 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 00000000..f1362642 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/sign/BcSignedDataGenerator.java @@ -0,0 +1,247 @@ +/* + * Copyright (c) 2022 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.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 com.ohos.hapsigntool.codesigning.utils.DigestUtils; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.bouncycastle.asn1.ASN1Encodable; +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[] generateSignData(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), createBerSetFormLst(signConfig.getCertificates()), + createBerSetFormLst(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 createBerSetFormLst(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 00000000..6698b0ec --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/sign/CentralDirectory.java @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2022 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 name 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 name 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; + + public final char compressionMethod; + + public final char fileNameLength; + + public final char extraFiledLength; + + public final char fileCommentLength; + + private final int relativeOffsetOfLocalHeader; + + private final byte[] fileName; + + public CentralDirectory(char compressionMethod, char fileNameLength, char extraFiledLength, char fileCommentLength, + int relativeOffsetOfLocalHeader, byte[] fileName) { + this.compressionMethod = compressionMethod; + this.fileNameLength = fileNameLength; + this.extraFiledLength = extraFiledLength; + this.fileCommentLength = fileCommentLength; + this.relativeOffsetOfLocalHeader = relativeOffsetOfLocalHeader; + this.fileName = fileName; + } + + /** + * Return true if entry is an executable file, i.e. anc 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 getExtraFiledLength() { + return extraFiledLength; + } + + /** + * 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), extraFiledLength(%d), fileCommentLength(%d)", (int) this.compressionMethod, + this.getFileName(), this.relativeOffsetOfLocalHeader, (int) this.fileNameLength, + (int) this.extraFiledLength, (int) this.fileCommentLength); + } +} 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 00000000..c8dc5610 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/sign/CodeSigning.java @@ -0,0 +1,404 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ohos.hapsigntool.codesigning.sign; + +import com.ohos.hapsigntool.codesigning.datastructure.CodeSignBlock; +import com.ohos.hapsigntool.codesigning.datastructure.ElfSignBlock; +import com.ohos.hapsigntool.codesigning.datastructure.Extension; +import com.ohos.hapsigntool.codesigning.datastructure.FsVerityInfoSegment; +import com.ohos.hapsigntool.codesigning.datastructure.MerkleTreeExtension; +import com.ohos.hapsigntool.codesigning.datastructure.SignInfo; +import com.ohos.hapsigntool.codesigning.exception.CodeSignException; +import com.ohos.hapsigntool.codesigning.exception.FsVerityDigestException; +import com.ohos.hapsigntool.codesigning.fsverity.FsVerityDescriptor; +import com.ohos.hapsigntool.codesigning.fsverity.FsVerityGenerator; +import com.ohos.hapsigntool.codesigning.utils.HapUtils; +import com.ohos.hapsigntool.hap.config.SignerConfig; +import com.ohos.hapsigntool.hap.entity.Pair; +import com.ohos.hapsigntool.hap.exception.HapFormatException; +import com.ohos.hapsigntool.signer.LocalSigner; +import com.ohos.hapsigntool.zip.RandomAccessFileZipDataInput; +import com.ohos.hapsigntool.zip.ZipDataInput; +import com.ohos.hapsigntool.zip.ZipFileInfo; +import com.ohos.hapsigntool.zip.ZipUtils; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +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_FROM = "hap/hsp"; + + public static final String SUPPORT_BIN_FILE_FROM = "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); + + public static final String NATIVE_LIB_AN_SUFFIX = ".an"; + + public 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_FROM.contains(inForm)) { + throw new CodeSignException("file's format is unsupported"); + } + ElfSignBlock signBlock = new ElfSignBlock.ElfSignBlockBuilder().build(); + long fileSize = input.length(); + long fsvTreeOffset = signBlock.computeMerkleTreeOffset(offset); + // add fs-verify info + signBlock.addFsVerityInfo(FsVerityDescriptor.VERSION, FsVerityGenerator.getFsVerityHashAlgorithm(), + FsVerityGenerator.getLog2BlockSize()); + try (FileInputStream inputStream = new FileInputStream(input)) { + FsVerityGenerator fsVerityGenerator = new FsVerityGenerator(); + fsVerityGenerator.generateFsVerityDigest(inputStream, fileSize, fsvTreeOffset); + byte[] fsVerityDigest = fsVerityGenerator.getFsVerityDigest(); + byte[] signature = generateSignature(fsVerityDigest); + // add sign info + signBlock.addSignInfo(fsVerityGenerator.getSaltSize(), FsVerityDescriptor.FLAG_STORE_MERKLE_TREE_OFFSET, + fileSize, fsVerityGenerator.getSalt(), signature); + //add merkle tree info + signBlock.addMerkleTreeInfo(fsVerityGenerator.getTreeBytes(), fsvTreeOffset, + fsVerityGenerator.getRootHash()); + LOGGER.info("Sign successfully."); + return signBlock.toByteArray(); + } + } + + /** + * Sign the given hap file, and pack all signature into output file + * + * @param input file to sign + * @param offset position of codesign block based on start of the file + * @param inForm file's format + * @return byte array of code sign block + * @throws CodeSignException code signing exception + * @throws IOException io error + * @throws HapFormatException hap format invalid + * @throws FsVerityDigestException computing FsVerity digest error + */ + public byte[] getCodeSignBlock(File input, long offset, String inForm) + throws CodeSignException, IOException, HapFormatException, FsVerityDigestException { + LOGGER.info("Start to sign code."); + if (SUPPORT_BIN_FILE_FROM.contains(inForm)) { + return getElfCodeSignBlock(input, offset, inForm); + } + if (!SUPPORT_FILE_FROM.contains(inForm)) { + throw new CodeSignException("file's format is unsupported"); + } + long dataSize = computeDataSize(input); + timestamp = System.currentTimeMillis(); + // generate CodeSignBlock + this.codeSignBlock = new CodeSignBlock(); + // compute merkle tree offset, replace with computeMerkleTreeOffset if fs-verity descriptor supports + long fsvTreeOffset = this.codeSignBlock.computeMerkleTreeOffset(offset); + // update fs-verity segment + FsVerityInfoSegment fsVerityInfoSegment = new FsVerityInfoSegment(FsVerityDescriptor.VERSION, + FsVerityGenerator.getFsVerityHashAlgorithm(), FsVerityGenerator.getLog2BlockSize()); + this.codeSignBlock.setFsVerityInfoSegment(fsVerityInfoSegment); + + LOGGER.debug("Sign hap."); + FileInputStream inputStream = new FileInputStream(input); + Pair hapSignInfoAndMerkleTreeBytesPair = signFile(inputStream, dataSize, true, fsvTreeOffset); + // update hap segment in CodeSignBlock + this.codeSignBlock.getHapInfoSegment().setSignInfo(hapSignInfoAndMerkleTreeBytesPair.getFirst()); + // Insert merkle tree bytes into code sign block + this.codeSignBlock.addOneMerkleTree(HAP_SIGNATURE_ENTRY_NAME, hapSignInfoAndMerkleTreeBytesPair.getSecond()); + + //update native lib info segment in CodeSignBlock + signNativeLibs(input); + + // last update codeSignBlock before generating ths byte array representation + updateCodeSignBlock(this.codeSignBlock); + + // complete code sign block byte array here + byte[] generated = this.codeSignBlock.generateCodeSignBlockByte(fsvTreeOffset); + LOGGER.info("Sign successfully"); + return generated; + } + + private long computeDataSize(File file) throws IOException, HapFormatException { + // parse central directory + RandomAccessFile outputHap = new RandomAccessFile(file, "rw"); + ZipDataInput outputHapIn = new RandomAccessFileZipDataInput(outputHap); + ZipFileInfo zipInfo = ZipUtils.findZipInfo(outputHapIn); + long centralDirectoryOffset = zipInfo.getCentralDirectoryOffset(); + int centralDirectorySize = zipInfo.getCentralDirectorySize(); + int centralDirectoryEntryCount = zipInfo.getCentralDirectoryEntryCount(); + // centralDirectoryOffset is where all data ends, including abc/so/an and resources + FileInputStream input = new FileInputStream(file); + input.skip(centralDirectoryOffset); + byte[] centralDirectoryBuffer = new byte[centralDirectorySize]; + input.read(centralDirectoryBuffer); + List cdList = parseCentralDirectory(centralDirectoryBuffer, centralDirectoryEntryCount); + long dataSize = 0L; + for (CentralDirectory entry : cdList) { + if (!(entry.isCodeFile() && entry.isUncompressed())) { + // if the first file is not uncompressed abc or so, set dataSize to zero + if (entry.getRelativeOffsetOfLocalHeader() == 0) { + dataSize = 0; + break; + } + // the first entry which is not abc/so/an is found, return its data offset + dataSize = entry.getRelativeOffsetOfLocalHeader() + JarFile.LOCHDR + entry.getFileNameLength() + + entry.getExtraFiledLength(); + break; + } + } + if ((dataSize % CodeSignBlock.PAGE_SIZE_4K) != 0) { + throw new HapFormatException( + String.format(Locale.ROOT, "Invalid dataSize(%d), not a multiple of 4096", dataSize)); + } + return dataSize; + } + + private void signNativeLibs(File input) throws IOException, FsVerityDigestException, CodeSignException { + // 'an' libs are always signed + extractedNativeLibSuffixs.add(NATIVE_LIB_AN_SUFFIX); + if (HapUtils.checkCompressNativeLibs(input)) { + LOGGER.info("compressNativeLibs equals true, sign so libs as well."); + // sign so libs only if compressNativeLibs equals true + extractedNativeLibSuffixs.add(NATIVE_LIB_SO_SUFFIX); + } + + // sign native files + JarFile inputJar = new JarFile(input, false); + List entryNames = getNativeEntriesFromHap(inputJar); + if (entryNames.isEmpty()) { + LOGGER.info("No native libs."); + return; + } + List> nativeLivInfoList = signFilesFromJar(entryNames, inputJar); + // update SoInfoSegment in CodeSignBlock + this.codeSignBlock.getSoInfoSegment().setSoInfoList(nativeLivInfoList); + } + + /** + * 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 tess 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 form 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 fsvTessOffset 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 fsvTessOffset) throws FsVerityDigestException, CodeSignException { + FsVerityGenerator fsVerityGenerator = new FsVerityGenerator(); + fsVerityGenerator.generateFsVerityDigest(inputStream, fileSize, fsvTessOffset); + 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, fsvTessOffset, + 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.generateSignData(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. + */ + 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[] byteaBeforeCompressionMethod = new byte[CentralDirectory.BYTE_SIZE_BEFORE_COMPRESSION_METHOD]; + cdBuffer.get(byteaBeforeCompressionMethod); + char compressionMode = cdBuffer.getChar(); + 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(); + 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 = new CentralDirectory(compressionMode, fileNameLength, extraFieldLength, + fileCommentLength, locHdrOffset, fileNameBuffer); + cdList.add(cd); + } + + return cdList; + } + + private void printErrorLog(Exception exception) { + if (exception != null) { + LOGGER.error("Code signing error: {}", exception.getMessage(), exception); + } + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/sign/SignedDataGenerator.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/sign/SignedDataGenerator.java new file mode 100644 index 00000000..bfc848d3 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/sign/SignedDataGenerator.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2022 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 + */ +public interface SignedDataGenerator { + /** + * Create a BcSignedDataGenerator instance + */ + SignedDataGenerator BC = new BcSignedDataGenerator(); + + /** + * Generate signature data with specific content and sign configuration + * + * @param content unsigned dile digest content. + * @param signerConfig sign configurations + * @return signed data. + * @throws CodeSignException if error. + */ + byte[] generateSignData(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 00000000..011af9ad --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/sign/VerifyCodeSignature.java @@ -0,0 +1,311 @@ +/* + * Copyright (c) 2022 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); + + public static final String NATIVE_LIB_AN_SUFFIX = ".an"; + + public 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_FROM.contains(fileFormat)) { + LOGGER.info("Not elf file, skip code signing verify"); + return true; + } + // 1) parse sign block to ElfCodeSignBlock object + ElfSignBlock elfSignBlock; + try (FileInputStream signedElf = new FileInputStream(file)) { + byte[] codeSignBlockBytes = new byte[(int) length]; + signedElf.skip(offset); + signedElf.read(codeSignBlockBytes); + elfSignBlock = ElfSignBlock.fromByteArray(codeSignBlockBytes, offset); + } + // 2) verify file data + try (FileInputStream signedElf = new FileInputStream(file)) { + verifySingleFile(signedElf, elfSignBlock.getDataSize(), elfSignBlock.getSignature(), + elfSignBlock.getTreeOffset(), elfSignBlock.getMerkleTreeData()); + } + return true; + } + + /** + * Verify a signed hap's signature + * + * @param file signed hap file + * @param offset start position of code sign block based on the start of the hap file + * @param length byte size of code sign block + * @param fileFormat hap or hqf or hsp, etc. + * @return true if signature verify succeed and false otherwise + * @throws IOException If an input or output exception occurred + * @throws VerifyCodeSignException parsing result invalid + * @throws FsVerityDigestException if fs-verity digest generation failed + * @throws CMSException if signature verify failed + */ + public static boolean verifyHap(File file, long offset, long length, String fileFormat) + throws IOException, VerifyCodeSignException, FsVerityDigestException, CMSException { + if (!CodeSigning.SUPPORT_FILE_FROM.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 = generateFsVerifyDigest(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 generateFsVerifyDigest(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 00000000..a9e9ba56 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/utils/CmsUtils.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2022 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 00000000..9414e8d7 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/utils/DigestUtils.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2022 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 00000000..41a4240f --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/utils/HapUtils.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2022 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 00000000..98e4c2fe --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/utils/InputStreamUtils.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2022 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 in 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; + } +} -- Gitee From 4d7013729f7c3f538bce5969821ff2fd3a25c603 Mon Sep 17 00:00:00 2001 From: zfeixiang Date: Tue, 24 Oct 2023 22:10:01 +0800 Subject: [PATCH 04/15] =?UTF-8?q?=E5=BA=94=E7=94=A8=E5=8F=8A=E4=BA=8C?= =?UTF-8?q?=E8=BF=9B=E5=88=B6=E6=96=87=E4=BB=B6=E4=BB=A3=E7=A0=81=E7=AD=BE?= =?UTF-8?q?=E5=90=8D=E8=83=BD=E5=8A=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: zfeixiang --- .../datastructure/CodeSignBlock.java | 270 ++++++++++ .../datastructure/CodeSignBlockHeader.java | 194 ++++++++ .../datastructure/ElfSignBlock.java | 468 ++++++++++++++++++ .../codesigning/datastructure/Extension.java | 76 +++ .../datastructure/FsVerityInfoSegment.java | 155 ++++++ .../datastructure/HapInfoSegment.java | 139 ++++++ .../datastructure/MerkleTreeExtension.java | 144 ++++++ .../datastructure/NativeLibInfoSegment.java | 283 +++++++++++ .../datastructure/SegmentHeader.java | 156 ++++++ .../codesigning/datastructure/SignInfo.java | 453 +++++++++++++++++ .../datastructure/SignedFilePos.java | 123 +++++ .../exception/CodeSignException.java | 44 ++ .../exception/FsVerityDigestException.java | 33 ++ .../exception/VerifyCodeSignException.java | 45 ++ .../fsverity/FsVerityDescriptor.java | 120 +++++ .../codesigning/fsverity/FsVerityDigest.java | 52 ++ .../fsverity/FsVerityGenerator.java | 158 ++++++ .../fsverity/FsVerityHashAlgorithm.java | 50 ++ .../codesigning/fsverity/MerkleTree.java | 44 ++ .../fsverity/MerkleTreeBuilder.java | 354 +++++++++++++ .../sign/BcSignedDataGenerator.java | 246 +++++++++ .../codesigning/sign/CentralDirectory.java | 119 +++++ .../codesigning/sign/CodeSigning.java | 406 +++++++++++++++ .../codesigning/sign/SignedDataGenerator.java | 41 ++ .../codesigning/sign/VerifyCodeSignature.java | 311 ++++++++++++ .../codesigning/utils/CmsUtils.java | 86 ++++ .../codesigning/utils/DigestUtils.java | 40 ++ .../codesigning/utils/HapUtils.java | 106 ++++ .../codesigning/utils/InputStreamUtils.java | 64 +++ 29 files changed, 4780 insertions(+) create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/CodeSignBlock.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/CodeSignBlockHeader.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/ElfSignBlock.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/Extension.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/FsVerityInfoSegment.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/HapInfoSegment.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/MerkleTreeExtension.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/NativeLibInfoSegment.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/SegmentHeader.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/SignInfo.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/SignedFilePos.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/exception/CodeSignException.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/exception/FsVerityDigestException.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/exception/VerifyCodeSignException.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/fsverity/FsVerityDescriptor.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/fsverity/FsVerityDigest.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/fsverity/FsVerityGenerator.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/fsverity/FsVerityHashAlgorithm.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/fsverity/MerkleTree.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/fsverity/MerkleTreeBuilder.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/sign/BcSignedDataGenerator.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/sign/CentralDirectory.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/sign/CodeSigning.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/sign/SignedDataGenerator.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/sign/VerifyCodeSignature.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/utils/CmsUtils.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/utils/DigestUtils.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/utils/HapUtils.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/utils/InputStreamUtils.java diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/CodeSignBlock.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/CodeSignBlock.java new file mode 100644 index 00000000..50d9719a --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/CodeSignBlock.java @@ -0,0 +1,270 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ohos.hapsigntool.codesigning.datastructure; + +import com.ohos.hapsigntool.codesigning.sign.CodeSigning; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +/** + * code sign block is a chunk of bytes attached to hap package or file. + * It consists of two headers: + * 1) code sign block header + * 2) segment header + * three segments: + * 1) fs-verity info segment + * 2) hap info segment + * 3) so info segment + * one zero padding area in order to align merkle tree raw bytes + * 1) zero padding + * and one area storing merkle tree bytes: + * 1) merkle tree raw bytes + *

+ * After signing a hap, call toByteArray() method to generate a block of bytes + * + * @since 2023/09/08 + */ +public class CodeSignBlock { + /** + * page size in bytes + */ + public static final long PAGE_SIZE_4K = 4096L; + + /** + * Segment header count, including fs-verity info, hap info, so info segment + */ + public static final int SEGMENT_HEADER_COUNT = 3; + + private CodeSignBlockHeader codeSignBlockHeader; + + private final List segmentHeaderList; + + private FsVerityInfoSegment fsVerityInfoSegment; + + private HapInfoSegment hapInfoSegment; + + private NativeLibInfoSegment nativeLibInfoSegment; + + private byte[] zeroPadding; + + private final Map merkleTreeMap; + + public CodeSignBlock() { + this.codeSignBlockHeader = new CodeSignBlockHeader(); + this.segmentHeaderList = new ArrayList<>(); + this.fsVerityInfoSegment = new FsVerityInfoSegment(); + this.hapInfoSegment = new HapInfoSegment(); + this.nativeLibInfoSegment = new NativeLibInfoSegment(); + this.merkleTreeMap = new HashMap<>(); + } + + /** + * Add one merkle tree into merkleTreeMap + * + * @param key file name + * @param merkleTree merkle tree raw bytes + */ + public void addOneMerkleTree(String key, byte[] merkleTree) { + if (merkleTree == null) { + this.merkleTreeMap.put(key, new byte[0]); + } else { + this.merkleTreeMap.put(key, merkleTree); + } + } + + /** + * Get one merkle tree from merkleTreeMap by file name + * + * @param key file name + * @return merkle tree bytes + */ + public byte[] getOneMerkleTreeByFileName(String key) { + return this.merkleTreeMap.get(key); + } + + /** + * set code sign block flag + */ + public void setCodeSignBlockFlag() { + int flags = CodeSignBlockHeader.FLAG_MERKLE_TREE_INLINED; + if (this.nativeLibInfoSegment.getSectionNum() != 0) { + flags += CodeSignBlockHeader.FLAG_NATIVE_LIB_INCLUDED; + } + this.codeSignBlockHeader.setFlags(flags); + } + + /** + * set segmentNum defined in code sign block header, equals length if segmentHeaderList + */ + public void setSegmentNum() { + this.codeSignBlockHeader.setSegmentNum(segmentHeaderList.size()); + } + + /** + * add one segment to segmentHeaderList + * + * @param sh segment header + */ + public void addToSegmentList(SegmentHeader sh) { + this.segmentHeaderList.add(sh); + } + + public List getSegmentHeaderList() { + return segmentHeaderList; + } + + /** + * set segment header list + */ + public void setSegmentHeaders() { + // fs-verity info segment + segmentHeaderList.add(new SegmentHeader(SegmentHeader.CSB_FSVERITY_INFO_SEG, this.fsVerityInfoSegment.size())); + // hap info segment + segmentHeaderList.add(new SegmentHeader(SegmentHeader.CSB_HAP_META_SEG, this.hapInfoSegment.size())); + // native lib info segment + segmentHeaderList.add( + new SegmentHeader(SegmentHeader.CSB_NATIVE_LIB_INFO_SEG, this.nativeLibInfoSegment.size())); + } + + public CodeSignBlockHeader getCodeSignBlockHeader() { + return codeSignBlockHeader; + } + + public void setCodeSignBlockHeader(CodeSignBlockHeader csbHeader) { + this.codeSignBlockHeader = csbHeader; + } + + public void setFsVerityInfoSegment(FsVerityInfoSegment fsVeritySeg) { + this.fsVerityInfoSegment = fsVeritySeg; + } + + public FsVerityInfoSegment getFsVerityInfoSegment() { + return fsVerityInfoSegment; + } + + public HapInfoSegment getHapInfoSegment() { + return hapInfoSegment; + } + + public void setHapInfoSegment(HapInfoSegment hapSeg) { + this.hapInfoSegment = hapSeg; + } + + public NativeLibInfoSegment getSoInfoSegment() { + return nativeLibInfoSegment; + } + + public void setSoInfoSegment(NativeLibInfoSegment soSeg) { + this.nativeLibInfoSegment = soSeg; + } + + /** + * Convert code sign block object to a newly created byte array + * + * @return Byte array representation of a CodeSignBlock object + */ + public byte[] toByteArray() { + ByteBuffer bf = ByteBuffer.allocate(this.codeSignBlockHeader.getBlockSize()).order(ByteOrder.LITTLE_ENDIAN); + bf.put(this.codeSignBlockHeader.toByteArray()); + for (SegmentHeader sh : this.segmentHeaderList) { + bf.put(sh.toByteArray()); + } + bf.put(this.zeroPadding); + // Hap merkle tree + if (this.hapInfoSegment.getSignInfo().getExtensionByType(MerkleTreeExtension.MERKLE_TREE_INLINED) != null) { + bf.put(merkleTreeMap.get("Hap")); + } + bf.put(this.fsVerityInfoSegment.toByteArray()); + bf.put(this.hapInfoSegment.toByteArray()); + bf.put(this.nativeLibInfoSegment.toByteArray()); + return bf.array(); + } + + /** + * SegmentOffset is the position of each segment defined in segmentHeaderList, + * based on the start position of code sign block + */ + public void computeSegmentOffset() { + // 1) the first segment is placed after merkle tree + int segmentOffset = CodeSignBlockHeader.size() + + this.segmentHeaderList.size() * SegmentHeader.SEGMENT_HEADER_LENGTH + this.zeroPadding.length + + this.getOneMerkleTreeByFileName(CodeSigning.HAP_SIGNATURE_ENTRY_NAME).length; + for (SegmentHeader sh : segmentHeaderList) { + sh.setSegmentOffset(segmentOffset); + segmentOffset += sh.getSegmentSize(); + } + } + + /** + * Compute the offset to store merkle tree raw bytes based on file start + * + * @param codeSignBlockOffset offset to store code sign block based on file start + * @return offset to store merkle tree based on the file start, it includes the codeSignBlockOffset + */ + public long computeMerkleTreeOffset(long codeSignBlockOffset) { + long sizeWithoutMerkleTree = CodeSignBlockHeader.size() + + SEGMENT_HEADER_COUNT * SegmentHeader.SEGMENT_HEADER_LENGTH; + // add code sign block offset while computing align position for merkle tree + long residual = (codeSignBlockOffset + sizeWithoutMerkleTree) % PAGE_SIZE_4K; + if (residual == 0) { + this.zeroPadding = new byte[0]; + } else { + this.zeroPadding = new byte[(int) (PAGE_SIZE_4K - residual)]; + } + return codeSignBlockOffset + sizeWithoutMerkleTree + zeroPadding.length; + } + + /** + * Convert CodeSignBlock to bytes + * + * @param fsvTreeOffset merkle tree offset + * @return byte array representing the code sign block + */ + public byte[] generateCodeSignBlockByte(long fsvTreeOffset) { + // 1) compute overall block size without merkle tree + long csbSize = CodeSignBlockHeader.size() + + (long) this.segmentHeaderList.size() * SegmentHeader.SEGMENT_HEADER_LENGTH + this.zeroPadding.length + + this.getOneMerkleTreeByFileName(CodeSigning.HAP_SIGNATURE_ENTRY_NAME).length + + this.fsVerityInfoSegment.size() + this.hapInfoSegment.size() + this.nativeLibInfoSegment.size(); + Extension ext = this.hapInfoSegment.getSignInfo().getExtensionByType(MerkleTreeExtension.MERKLE_TREE_INLINED); + if (ext instanceof MerkleTreeExtension) { + MerkleTreeExtension merkleTreeExtension = (MerkleTreeExtension) ext; + merkleTreeExtension.setMerkleTreeOffset(fsvTreeOffset); + } + this.codeSignBlockHeader.setBlockSize(csbSize); + // 2) generate byte array of complete code sign block + return toByteArray(); + } + + /** + * Return a string representation of the object + * + * @return string representation of the object + */ + public String toString() { + return String.format(Locale.ROOT, + "CodeSignBlockHeader[%s], SegmentHeaders[%s], FsVeritySeg[%s], HapInfoSeg[%s], SoInfoSeg[%s]", + this.codeSignBlockHeader, Arrays.toString(this.segmentHeaderList.toArray()), this.fsVerityInfoSegment, + this.hapInfoSegment, this.nativeLibInfoSegment); + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/CodeSignBlockHeader.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/CodeSignBlockHeader.java new file mode 100644 index 00000000..334a4089 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/CodeSignBlockHeader.java @@ -0,0 +1,194 @@ +/* + * Copyright (c) 2022 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 = 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]; + + /** + * Default construct of CodeSignBlockHeader + */ + public CodeSignBlockHeader() { + } + + /** + * Construct of CodeSignBlockHeader + * + * @param magic magic number + * @param version version + * @param blockSize code sign block size + * @param segmentNum segment number + * @param flags flags + * @param reserved reserved + */ + public CodeSignBlockHeader(long magic, int version, int blockSize, int segmentNum, int flags, byte[] reserved) { + this.magic = magic; + this.version = version; + this.blockSize = blockSize; + this.segmentNum = segmentNum; + this.flags = flags; + this.reserved = 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 segmentNum of CodeSignBlockHeader"); + } + byte[] inReserved = new byte[RESERVED_BYTE_ARRAY_LENGTH]; + bf.get(inReserved); + return new CodeSignBlockHeader(inMagic, inVersion, inBlockSize, inSegmentNum, inFlags, inReserved); + } + + /** + * 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); + } +} 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 00000000..a523beff --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/ElfSignBlock.java @@ -0,0 +1,468 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ohos.hapsigntool.codesigning.datastructure; + +import com.ohos.hapsigntool.codesigning.exception.VerifyCodeSignException; +import com.ohos.hapsigntool.codesigning.fsverity.FsVerityDescriptor; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Arrays; + +/** + * elf sign block is a chunk of bytes attached to elf file. + * 1) u32 type: 0x2 merkle tree + * 2) u32 length: merkle tree with padding size + * 3) u8[] merkle tree data + * 4) u32 type: 0x1 fsverity descriptor + * 5) u32 length: fsverity descriptor size + * 6) u8 version: fs-verity version + * 7) u8 hashAlgorithm: hash algorithm to use for the Merkle tree + * 8) u8 log2BlockSize: log2 of size of data and tree blocks + * 9) u8 saltSize: byte size of salt + * 10) u32 signSize: byte size of signature + * 11) u64 dataSize: byte size of data being signed + * 12) u8[64] rootHash: merkle tree root hash + * 13) u8[32] salt: salt used in signing + * 14) u32 flags + * 15) u32 reserved + * 16) u64 treeOffset: merkle tree offset + * 17) u8[128] reserved + * 18) u8[] 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 static final int ROOT_HASH_SIZE = 64; + + private static final int SALT_BUFFER_LENGTH = 32; + + private static final int RESERVED_BYTE_ARRAY_LENGTH = 128; + + private static final int FSD_WITHOUT_SIGN_LENGTH = 256; + + private int treeType; + + private int treeLength; + + // zero padding before merkle tree + private byte[] paddingBeforeTree; + + private byte[] merkleTreeData; + + private int fsdType; + + private int fsdLength; + + private byte fsVersion; + + private byte fsHashAlgorithm; + + private byte log2BlockSize; + + private byte saltSize; + + private int signSize; + + private long dataSize; + + private byte[] rootHash; + + private byte[] salt; + + private int flags; + + private int reservedInt; + + private long treeOffset; + + private byte[] reservedData; + + private byte[] signature; + + private ElfSignBlock(ElfSignBlockBuilder builder) { + this.treeType = builder.treeType; + this.treeLength = builder.treeLength; + this.paddingBeforeTree = builder.paddingBeforeTree; + this.merkleTreeData = builder.merkleTreeData; + this.fsdType = builder.fsdType; + this.fsdLength = builder.fsdLength; + this.fsVersion = builder.fsVersion; + this.fsHashAlgorithm = builder.fsHashAlgorithm; + this.log2BlockSize = builder.log2BlockSize; + this.saltSize = builder.saltSize; + this.signSize = builder.signSize; + this.dataSize = builder.dataSize; + this.rootHash = builder.rootHash; + this.salt = builder.salt; + this.flags = builder.flags; + this.reservedInt = builder.reservedInt; + this.treeOffset = builder.treeOffset; + this.reservedData = builder.reservedData; + this.signature = builder.signature; + } + + /** + * Return the byte size of code sign block + * + * @return byte size of code sign block + */ + public int size() { + return Integer.BYTES * 2 + paddingBeforeTree.length + merkleTreeData.length + Integer.BYTES * 2 + + FSD_WITHOUT_SIGN_LENGTH + signature.length; + } + + /** + * add fs-verity info + * + * @param version fs-verity version + * @param hashAlgorithm fs-verity hash algorithm id + * @param log2BlockSize log2 of hash block size + */ + public void addFsVerityInfo(byte version, byte hashAlgorithm, byte log2BlockSize) { + this.fsVersion = version; + this.fsHashAlgorithm = hashAlgorithm; + this.log2BlockSize = log2BlockSize; + } + + /** + * add sign info + * + * @param saltSize byte size of salt + * @param flags reserved flags + * @param fileSize byte size of data being signed + * @param salt salt in byte array representation + * @param signature signature after signing the data in byte array representation + */ + + public void addSignInfo(int saltSize, int flags, long fileSize, byte[] salt, byte[] signature) { + this.saltSize = (byte) saltSize; + this.flags = flags; + this.dataSize = fileSize; + if (salt != null) { + this.salt = Arrays.copyOf(salt, SALT_BUFFER_LENGTH); + } + if (signature != null) { + this.signature = signature; + } + this.signSize = this.signature.length; + this.fsdLength = FSD_WITHOUT_SIGN_LENGTH + signSize; + } + + /** + * add merkle tree info + * + * @param merkleTreeBytes merkle tree data + * @param fsvTreeOffset merkle tree offset based on file start + * @param rootHash Root hash of the merkle tree + */ + public void addMerkleTreeInfo(byte[] merkleTreeBytes, long fsvTreeOffset, byte[] rootHash) { + if (merkleTreeBytes != null) { + this.merkleTreeData = merkleTreeBytes; + } + this.treeOffset = fsvTreeOffset; + if (rootHash != null) { + this.rootHash = Arrays.copyOf(rootHash, ROOT_HASH_SIZE); + } + treeLength = paddingBeforeTree.length + merkleTreeData.length; + } + + /** + * add padding length offset while computing align position for merkle tree + * + * @param signBlockOffset sign block offset based on the start of file + * @return merkle tree raw bytes offset based on the start of file + */ + public long computeMerkleTreeOffset(long signBlockOffset) { + long residual = (signBlockOffset + Integer.BYTES * 2) % PAGE_SIZE_4k; + if (residual > 0) { + this.paddingBeforeTree = new byte[(int) (PAGE_SIZE_4k - residual)]; + } + return signBlockOffset + Integer.BYTES * 2 + paddingBeforeTree.length; + } + + public byte[] getMerkleTreeData() { + return merkleTreeData; + } + + public long getDataSize() { + return dataSize; + } + + public long getTreeOffset() { + return treeOffset; + } + + public byte[] getSignature() { + return signature; + } + + /** + * Return a string representation of the object + * + * @return string representation of the object + */ + public byte[] toByteArray() { + ByteBuffer bf = ByteBuffer.allocate(size()).order(ByteOrder.LITTLE_ENDIAN); + bf.putInt(treeType); + bf.putInt(paddingBeforeTree.length + merkleTreeData.length); + bf.put(paddingBeforeTree); + bf.put(merkleTreeData); + bf.putInt(fsdType); + bf.putInt(fsdLength); + bf.put(fsVersion); + bf.put(fsHashAlgorithm); + bf.put(log2BlockSize); + bf.put(saltSize); + bf.putInt(signSize); + bf.putLong(dataSize); + bf.put(rootHash); + bf.put(salt); + bf.putInt(flags); + bf.putInt(reservedInt); + bf.putLong(treeOffset); + bf.put(reservedData); + bf.put(signature); + return bf.array(); + } + + /** + * Init the ElfSignBlock by a byte array + * + * @param bytes Byte array representation of a CodeSignBlockHeader object + * @param signBlockOffset start position of code sign block based on the start of the elf file + * @return a newly created CodeSignBlockHeader object + * @throws VerifyCodeSignException parse result invalid + */ + public static ElfSignBlock fromByteArray(byte[] bytes, long signBlockOffset) throws VerifyCodeSignException { + ByteBuffer bf = ByteBuffer.allocate(bytes.length).order(ByteOrder.LITTLE_ENDIAN); + bf.put(bytes); + // after put, rewind is mandatory before get + bf.rewind(); + ElfSignBlockBuilder builder = new ElfSignBlockBuilder(); + int inTreeType = bf.getInt(); + if (MERKLE_TREE_INLINED != inTreeType) { + throw new VerifyCodeSignException("Invalid merkle tree type of ElfSignBlock"); + } + int inTreeLength = bf.getInt(); + builder.setTreeType(inTreeType).setTreeLength(inTreeLength); + int paddingLength = (int) (PAGE_SIZE_4k - (signBlockOffset + Integer.BYTES * 2) % PAGE_SIZE_4k); + byte[] zeroPadding = new byte[paddingLength]; + bf.get(zeroPadding); + byte[] treeWithoutPadding = new byte[inTreeLength - paddingLength]; + bf.get(treeWithoutPadding); + builder.setPaddingBeforeTree(zeroPadding).setMerkleTreeData(treeWithoutPadding); + int inFsdType = bf.getInt(); + if (FsVerityDescriptor.FS_VERITY_DESCRIPTOR_TYPE != inFsdType) { + throw new VerifyCodeSignException("Invalid fs-verify descriptor type of ElfSignBlock"); + } + int inFsdLength = bf.getInt(); + if (bytes.length != Integer.BYTES * 2 + inTreeLength + Integer.BYTES * 2 + inFsdLength) { + throw new VerifyCodeSignException("Invalid fs-verify descriptor with signature length of ElfSignBlock"); + } + builder.setFsdType(inFsdType).setFsdLength(inFsdLength); + byte inFsVersion = bf.get(); + if (FsVerityDescriptor.VERSION != inFsVersion) { + throw new VerifyCodeSignException("Invalid fs-verify descriptor version of ElfSignBlock"); + } + byte inFsHashAlgorithm = bf.get(); + byte inLog2BlockSize = bf.get(); + builder.setFsVersion(inFsVersion).setFsHashAlgorithm(inFsHashAlgorithm).setLog2BlockSize(inLog2BlockSize); + byte inSaltSize = bf.get(); + if (SALT_BUFFER_LENGTH != inSaltSize) { + throw new VerifyCodeSignException("Invalid salt size of ElfSignBlock"); + } + int inSignSize = bf.getInt(); + if (inFsdLength != inSignSize + FSD_WITHOUT_SIGN_LENGTH) { + throw new VerifyCodeSignException("Invalid sign size of ElfSignBlock"); + } + int inDataSize = bf.getInt(); + byte[] inRootHash = new byte[ROOT_HASH_SIZE]; + bf.get(inRootHash); + builder.setSaltSize(inSaltSize).setSignSize(inSignSize).setDataSize(inDataSize).setRootHash(inRootHash); + byte[] inSalt = new byte[inSaltSize]; + bf.get(inSalt); + int inFlags = bf.getInt(); + bf.getInt(); + long inTreeOffset = bf.getLong(); + if (inTreeOffset % PAGE_SIZE_4k != 0) { + throw new VerifyCodeSignException("Invalid merkle tree offset of ElfSignBlock"); + } + bf.get(new byte[RESERVED_BYTE_ARRAY_LENGTH]); + byte[] inSignature = new byte[inSignSize]; + bf.get(inSignature); + builder.setSalt(inSalt).setFlags(inFlags).setTreeOffset(inTreeOffset).setSignature(inSignature); + return builder.build(); + } + + /** + * Builder of ElfSignBlock class + */ + public static class ElfSignBlockBuilder { + private int treeType = MERKLE_TREE_INLINED; + + private int treeLength; + + // zero padding before merkle tree + private byte[] paddingBeforeTree = new byte[0]; + + private byte[] merkleTreeData = new byte[0]; + + private int fsdType = FsVerityDescriptor.FS_VERITY_DESCRIPTOR_TYPE; + + private int fsdLength = FSD_WITHOUT_SIGN_LENGTH; + + private byte fsVersion = FsVerityDescriptor.VERSION; + + private byte fsHashAlgorithm; + + private byte log2BlockSize; + + private byte saltSize; + + private int signSize; + + private long dataSize; + + private byte[] rootHash = new byte[ROOT_HASH_SIZE]; + + private byte[] salt = new byte[SALT_BUFFER_LENGTH]; + + private int flags; + + private int reservedInt = 0; + + private long treeOffset; + + private byte[] reservedData = new byte[RESERVED_BYTE_ARRAY_LENGTH]; + + private byte[] signature = new byte[0]; + + public ElfSignBlockBuilder setTreeType(int treeType) { + this.treeType = treeType; + return this; + } + + public ElfSignBlockBuilder setTreeLength(int treeLength) { + this.treeLength = treeLength; + return this; + } + + public ElfSignBlockBuilder setPaddingBeforeTree(byte[] paddingBeforeTree) { + this.paddingBeforeTree = paddingBeforeTree; + return this; + } + + public ElfSignBlockBuilder setMerkleTreeData(byte[] merkleTreeData) { + this.merkleTreeData = merkleTreeData; + return this; + } + + public ElfSignBlockBuilder setFsdType(int fsdType) { + this.fsdType = fsdType; + return this; + } + + public ElfSignBlockBuilder setFsdLength(int fsdLength) { + this.fsdLength = fsdLength; + return this; + } + + public ElfSignBlockBuilder setFsVersion(byte fsVersion) { + this.fsVersion = fsVersion; + return this; + } + + public ElfSignBlockBuilder setFsHashAlgorithm(byte fsHashAlgorithm) { + this.fsHashAlgorithm = fsHashAlgorithm; + return this; + } + + public ElfSignBlockBuilder setLog2BlockSize(byte log2BlockSize) { + this.log2BlockSize = log2BlockSize; + return this; + } + + public ElfSignBlockBuilder setSaltSize(byte saltSize) { + this.saltSize = saltSize; + return this; + } + + public ElfSignBlockBuilder setSignSize(int signSize) { + this.signSize = signSize; + return this; + } + + public ElfSignBlockBuilder setDataSize(long dataSize) { + this.dataSize = dataSize; + return this; + } + + public ElfSignBlockBuilder setRootHash(byte[] rootHash) { + this.rootHash = rootHash; + return this; + } + + public ElfSignBlockBuilder setSalt(byte[] salt) { + this.salt = salt; + return this; + } + + public ElfSignBlockBuilder setFlags(int flags) { + this.flags = flags; + return this; + } + + public ElfSignBlockBuilder setReservedInt(int reservedInt) { + this.reservedInt = reservedInt; + return this; + } + + public ElfSignBlockBuilder setTreeOffset(long treeOffset) { + this.treeOffset = treeOffset; + return this; + } + + public ElfSignBlockBuilder setReservedData(byte[] reservedData) { + this.reservedData = reservedData; + return this; + } + + public ElfSignBlockBuilder setSignature(byte[] signature) { + this.signature = signature; + return this; + } + + /** + * return a ElfSignBlock object + * + * @return a ElfSignBlock object + */ + public ElfSignBlock build() { + return new ElfSignBlock(this); + } + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/Extension.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/Extension.java new file mode 100644 index 00000000..538ba380 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/Extension.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2022 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 00000000..8043f07a --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/FsVerityInfoSegment.java @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2022 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 00000000..8106a016 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/HapInfoSegment.java @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2022 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 00000000..dd863a96 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/MerkleTreeExtension.java @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2022 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 multiple of 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 00000000..49edd4d7 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/NativeLibInfoSegment.java @@ -0,0 +1,283 @@ +/* + * Copyright (c) 2022 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 = MAGIC_NUM; + + private int segmentSize; + + private int sectionNum; + + private List> soInfoList = new ArrayList<>(); + + private List signedFilePosList = new ArrayList<>(); + + private List fileNameList = new ArrayList<>(); + + private List signInfoList = new ArrayList<>(); + + private byte[] zeroPadding = new byte[0]; + + private int fileNameListBlockSize; + + private int signInfoListBlockSize; + + /** + * Default constructor + */ + public NativeLibInfoSegment() { + } + + /** + * Constructor for SoInfoSegment by byte array + * + * @param magic byte array representation of SoInfoSegment object + * @param segmentSize byte size of NativeLibInfoSegment + * @param sectionNum number of native libs + * @param signedFilePosList offset and size of each file + * @param fileNameList file name of each signed file + * @param signInfoList sign info of each signed file + * @param zeroPadding zero padding before sign info list + */ + public NativeLibInfoSegment(int magic, int segmentSize, int sectionNum, List signedFilePosList, + List fileNameList, List signInfoList, byte[] zeroPadding) { + this.magic = magic; + this.segmentSize = segmentSize; + this.sectionNum = sectionNum; + this.signedFilePosList = signedFilePosList; + this.fileNameList = fileNameList; + this.signInfoList = signInfoList; + this.zeroPadding = zeroPadding; + } + + /** + * set soInfoList, generate fileNameList and signInfoList + * + * @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 += signInfoList.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 NativeLibInfoSegment(inMagic, inSegmentSize, inSectionNum, inSignedFilePosList, inFileNameList, + inSignInfoList, inZeroPadding); + } + + /** + * 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], signFileEntryList[%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())); + } +} 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 00000000..d3d9c2f5 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/SegmentHeader.java @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2022 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 lager 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 00000000..62a2f33f --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/SignInfo.java @@ -0,0 +1,453 @@ +/* + * Copyright (c) 2022 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 00000000..59163d71 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/SignedFilePos.java @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2022 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 00000000..8a89924c --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/exception/CodeSignException.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2022 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 00000000..309c09d0 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/exception/FsVerityDigestException.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2022 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 00000000..ad9dbc95 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/exception/VerifyCodeSignException.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2022 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 00000000..8f0c478d --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/fsverity/FsVerityDescriptor.java @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ohos.hapsigntool.codesigning.fsverity; + +import com.ohos.hapsigntool.codesigning.exception.FsVerityDigestException; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * Format of FsVerity descriptor + * uint8 verision + * uint8 hashAlgorithm + * uint8 log2BlockSize + * uint8 saltSize + * uint8[4] 0 + * le64 dataSize + * uint8[64] rootHash + * uint8[32] salt + * uint32 flags + * uint8[4] 0 + * uint64 treeOffset + * uint8[128] 0 + * + * @since 2023/06/05 + */ +public class FsVerityDescriptor { + /** + * fs-verify version, must be 1 + */ + public static final byte VERSION = 1; + + /** + * Indicating merkle tree offset is set in fs-verify descriptor + */ + public static final int FLAG_STORE_MERKLE_TREE_OFFSET = 0x1; + + /** + * Indicating fs-verify descriptor type + */ + public static final int FS_VERITY_DESCRIPTOR_TYPE = 0x1; + + private static final int DESCRIPTOR_SIZE = 256; + + private static final int ROOT_HASH_FILED_SIZE = 64; + + private static final int SALT_SIZE = 32; + + private static final int FIRST_RESERVED_SIZE = 4; + + private static final int RESERVED_SIZE_AFTER_FLAGS = 4; + + /** + * Get FsVerity descriptor + * + * @param fileSize size of input + * @param hashAlgorithm hash algorithm id + * @param log2BlockSize log2 of hash block size + * @param salt salt used for hash + * @param rawRootHash root hash of merkle tree + * @param flags flag indicating whether merkle tree offset is present in fs-verify descriptor + * @param merkleTreeOffset merkle tree offset based on file start + * @return bytes of descriptor + * @throws FsVerityDigestException if error + */ + public static byte[] getDescriptor(long fileSize, byte hashAlgorithm, byte log2BlockSize, byte[] salt, + byte[] rawRootHash, int flags, long merkleTreeOffset) throws FsVerityDigestException { + ByteBuffer buffer = ByteBuffer.allocate(DESCRIPTOR_SIZE).order(ByteOrder.LITTLE_ENDIAN); + buffer.put(VERSION); + buffer.put(hashAlgorithm); + buffer.put(log2BlockSize); + if (salt == null) { + buffer.put((byte) 0); + } else if (salt.length > SALT_SIZE) { + throw new FsVerityDigestException("Salt is too long"); + } else { + buffer.put((byte) salt.length); + } + writeBytesWithSize(buffer, null, FIRST_RESERVED_SIZE); + buffer.putLong(fileSize); + writeBytesWithSize(buffer, rawRootHash, ROOT_HASH_FILED_SIZE); + writeBytesWithSize(buffer, salt, SALT_SIZE); + buffer.putInt(flags); + writeBytesWithSize(buffer, null, RESERVED_SIZE_AFTER_FLAGS); + buffer.putLong(merkleTreeOffset); + return buffer.array(); + } + + /** + * Write bytes to ByteBuffer with specific size + * + * @param buffer target buffer + * @param src bytes to write + * @param size size of written bytes, fill 0 if src bytes is long enough + */ + private static void writeBytesWithSize(ByteBuffer buffer, byte[] src, int size) { + int pos = buffer.position(); + if (src != null) { + if (src.length > size) { + buffer.put(src, 0, size); + } else { + buffer.put(src); + } + } + buffer.position(pos + size); + } +} diff --git a/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 00000000..6fafdaaa --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/fsverity/FsVerityDigest.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2022 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 00000000..ef2008b3 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/fsverity/FsVerityGenerator.java @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ohos.hapsigntool.codesigning.fsverity; + +import com.ohos.hapsigntool.codesigning.exception.FsVerityDigestException; +import com.ohos.hapsigntool.codesigning.utils.DigestUtils; + +import java.io.IOException; +import java.io.InputStream; +import java.security.NoSuchAlgorithmException; + +/** + * FsVerity data generator supper class + * + * @since 2023/06/05 + */ +public class FsVerityGenerator { + /** + * FsVerity hash algorithm + */ + private static final FsVerityHashAlgorithm FS_VERITY_HASH_ALGORITHM = FsVerityHashAlgorithm.SHA256; + + private static final byte LOG_2_OF_FSVERITY_HASH_PAGE_SIZE = 12; + + /** + * salt for hashing one page + */ + protected byte[] salt = null; + + private byte[] fsVerityDigest = null; + + private byte[] treeBytes = null; + + private byte[] rootHash = null; + + /** + * generate merkle tree of given input + * + * @param inputStream input stream for generate merkle tree + * @param size total size of input stream + * @param fsVerityHashAlgorithm hash algorithm for FsVerity + * @return merkle tree + * @throws FsVerityDigestException if error + */ + public MerkleTree generateMerkleTree(InputStream inputStream, long size, + FsVerityHashAlgorithm fsVerityHashAlgorithm) throws FsVerityDigestException { + MerkleTree merkleTree; + try (MerkleTreeBuilder builder = new MerkleTreeBuilder()) { + merkleTree = builder.generateMerkleTree(inputStream, size, fsVerityHashAlgorithm); + } catch (IOException e) { + throw new FsVerityDigestException("IOException: " + e.getMessage()); + } catch (NoSuchAlgorithmException e) { + throw new FsVerityDigestException("Invalid algorithm:" + e.getMessage()); + } + return merkleTree; + } + + /** + * generate FsVerity digest of given input + * + * @param inputStream input stream for generate FsVerity digest + * @param size total size of input stream + * @param fsvTreeOffset merkle tree raw bytes offset based on the start of file + * @throws FsVerityDigestException if error + */ + public void generateFsVerityDigest(InputStream inputStream, long size, long fsvTreeOffset) + throws FsVerityDigestException { + MerkleTree merkleTree; + if (size == 0) { + merkleTree = new MerkleTree(null, null, FS_VERITY_HASH_ALGORITHM); + } else { + merkleTree = generateMerkleTree(inputStream, size, FS_VERITY_HASH_ALGORITHM); + } + int flags = fsvTreeOffset == 0 ? 0 : FsVerityDescriptor.FLAG_STORE_MERKLE_TREE_OFFSET; + byte[] fsVerityDescriptor = FsVerityDescriptor.getDescriptor(size, FS_VERITY_HASH_ALGORITHM.getId(), + LOG_2_OF_FSVERITY_HASH_PAGE_SIZE, salt, merkleTree.rootHash, flags, fsvTreeOffset); + 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 00000000..3948d895 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/fsverity/FsVerityHashAlgorithm.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2022 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 00000000..1c5d3b51 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/fsverity/MerkleTree.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2022 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 00000000..7e594863 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/fsverity/MerkleTreeBuilder.java @@ -0,0 +1,354 @@ +/* + * Copyright (c) 2022 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 = 0; + 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 count + */ + 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 00000000..f8d6a94d --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/sign/BcSignedDataGenerator.java @@ -0,0 +1,246 @@ +/* + * Copyright (c) 2022 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[] generateSignData(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), createBerSetFormLst(signConfig.getCertificates()), + createBerSetFormLst(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 createBerSetFormLst(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 00000000..b609c2bb --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/sign/CentralDirectory.java @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2022 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 name 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 name 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 extraFiledLength; + + private final char fileCommentLength; + + private final int relativeOffsetOfLocalHeader; + + private final byte[] fileName; + + public CentralDirectory(char compressionMethod, char fileNameLength, char extraFiledLength, char fileCommentLength, + int relativeOffsetOfLocalHeader, byte[] fileName) { + this.compressionMethod = compressionMethod; + this.fileNameLength = fileNameLength; + this.extraFiledLength = extraFiledLength; + this.fileCommentLength = fileCommentLength; + this.relativeOffsetOfLocalHeader = relativeOffsetOfLocalHeader; + this.fileName = fileName; + } + + /** + * Return true if entry is an executable file, i.e. anc 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 getExtraFiledLength() { + return extraFiledLength; + } + + /** + * 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), extraFiledLength(%d), fileCommentLength(%d)", (int) this.compressionMethod, + this.getFileName(), this.relativeOffsetOfLocalHeader, (int) this.fileNameLength, + (int) this.extraFiledLength, (int) this.fileCommentLength); + } +} 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 00000000..f099a4bb --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/sign/CodeSigning.java @@ -0,0 +1,406 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ohos.hapsigntool.codesigning.sign; + +import com.ohos.hapsigntool.codesigning.datastructure.CodeSignBlock; +import com.ohos.hapsigntool.codesigning.datastructure.ElfSignBlock; +import com.ohos.hapsigntool.codesigning.datastructure.Extension; +import com.ohos.hapsigntool.codesigning.datastructure.FsVerityInfoSegment; +import com.ohos.hapsigntool.codesigning.datastructure.MerkleTreeExtension; +import com.ohos.hapsigntool.codesigning.datastructure.SignInfo; +import com.ohos.hapsigntool.codesigning.exception.CodeSignException; +import com.ohos.hapsigntool.codesigning.exception.FsVerityDigestException; +import com.ohos.hapsigntool.codesigning.fsverity.FsVerityDescriptor; +import com.ohos.hapsigntool.codesigning.fsverity.FsVerityGenerator; +import com.ohos.hapsigntool.codesigning.utils.HapUtils; +import com.ohos.hapsigntool.hap.config.SignerConfig; +import com.ohos.hapsigntool.hap.entity.Pair; +import com.ohos.hapsigntool.hap.exception.HapFormatException; +import com.ohos.hapsigntool.signer.LocalSigner; +import com.ohos.hapsigntool.zip.RandomAccessFileZipDataInput; +import com.ohos.hapsigntool.zip.ZipDataInput; +import com.ohos.hapsigntool.zip.ZipFileInfo; +import com.ohos.hapsigntool.zip.ZipUtils; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; +import java.util.Locale; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +/** + * core functions of code signing + * + * @since 2023/06/05 + */ +public class CodeSigning { + /** + * Only hap and hsp bundle supports code signing, concatenate then with '/' for verification usage + */ + public static final String SUPPORT_FILE_FROM = "hap/hsp"; + + public static final String SUPPORT_BIN_FILE_FROM = "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_FROM.contains(inForm)) { + throw new CodeSignException("file's format is unsupported"); + } + ElfSignBlock signBlock = new ElfSignBlock.ElfSignBlockBuilder().build(); + long fileSize = input.length(); + long fsvTreeOffset = signBlock.computeMerkleTreeOffset(offset); + // add fs-verify info + signBlock.addFsVerityInfo(FsVerityDescriptor.VERSION, FsVerityGenerator.getFsVerityHashAlgorithm(), + FsVerityGenerator.getLog2BlockSize()); + try (FileInputStream inputStream = new FileInputStream(input)) { + FsVerityGenerator fsVerityGenerator = new FsVerityGenerator(); + fsVerityGenerator.generateFsVerityDigest(inputStream, fileSize, fsvTreeOffset); + byte[] fsVerityDigest = fsVerityGenerator.getFsVerityDigest(); + byte[] signature = generateSignature(fsVerityDigest); + // add sign info + signBlock.addSignInfo(fsVerityGenerator.getSaltSize(), FsVerityDescriptor.FLAG_STORE_MERKLE_TREE_OFFSET, + fileSize, fsVerityGenerator.getSalt(), signature); + // add merkle tree info + signBlock.addMerkleTreeInfo(fsVerityGenerator.getTreeBytes(), fsvTreeOffset, + fsVerityGenerator.getRootHash()); + LOGGER.info("Sign successfully."); + return signBlock.toByteArray(); + } + } + + /** + * Sign the given hap file, and pack all signature into output file + * + * @param input file to sign + * @param offset position of codesign block based on start of the file + * @param inForm file's format + * @return byte array of code sign block + * @throws CodeSignException code signing exception + * @throws IOException io error + * @throws HapFormatException hap format invalid + * @throws FsVerityDigestException computing FsVerity digest error + */ + public byte[] getCodeSignBlock(File input, long offset, String inForm) + throws CodeSignException, IOException, HapFormatException, FsVerityDigestException { + LOGGER.info("Start to sign code."); + if (SUPPORT_BIN_FILE_FROM.contains(inForm)) { + return getElfCodeSignBlock(input, offset, inForm); + } + if (!SUPPORT_FILE_FROM.contains(inForm)) { + throw new CodeSignException("file's format is unsupported"); + } + long dataSize = computeDataSize(input); + timestamp = System.currentTimeMillis(); + // generate CodeSignBlock + this.codeSignBlock = new CodeSignBlock(); + // compute merkle tree offset, replace with computeMerkleTreeOffset if fs-verity descriptor supports + long fsvTreeOffset = this.codeSignBlock.computeMerkleTreeOffset(offset); + // update fs-verity segment + FsVerityInfoSegment fsVerityInfoSegment = new FsVerityInfoSegment(FsVerityDescriptor.VERSION, + FsVerityGenerator.getFsVerityHashAlgorithm(), FsVerityGenerator.getLog2BlockSize()); + this.codeSignBlock.setFsVerityInfoSegment(fsVerityInfoSegment); + + LOGGER.debug("Sign hap."); + FileInputStream inputStream = new FileInputStream(input); + Pair hapSignInfoAndMerkleTreeBytesPair = signFile(inputStream, dataSize, true, fsvTreeOffset); + // update hap segment in CodeSignBlock + this.codeSignBlock.getHapInfoSegment().setSignInfo(hapSignInfoAndMerkleTreeBytesPair.getFirst()); + // Insert merkle tree bytes into code sign block + this.codeSignBlock.addOneMerkleTree(HAP_SIGNATURE_ENTRY_NAME, hapSignInfoAndMerkleTreeBytesPair.getSecond()); + + // update native lib info segment in CodeSignBlock + signNativeLibs(input); + + // last update codeSignBlock before generating ths byte array representation + updateCodeSignBlock(this.codeSignBlock); + + // complete code sign block byte array here + byte[] generated = this.codeSignBlock.generateCodeSignBlockByte(fsvTreeOffset); + LOGGER.info("Sign successfully"); + return generated; + } + + private long computeDataSize(File file) throws IOException, HapFormatException { + // parse central directory + RandomAccessFile outputHap = new RandomAccessFile(file, "rw"); + ZipDataInput outputHapIn = new RandomAccessFileZipDataInput(outputHap); + ZipFileInfo zipInfo = ZipUtils.findZipInfo(outputHapIn); + long centralDirectoryOffset = zipInfo.getCentralDirectoryOffset(); + int centralDirectorySize = zipInfo.getCentralDirectorySize(); + int centralDirectoryEntryCount = zipInfo.getCentralDirectoryEntryCount(); + // centralDirectoryOffset is where all data ends, including abc/so/an and resources + FileInputStream input = new FileInputStream(file); + input.skip(centralDirectoryOffset); + byte[] centralDirectoryBuffer = new byte[centralDirectorySize]; + input.read(centralDirectoryBuffer); + List cdList = parseCentralDirectory(centralDirectoryBuffer, centralDirectoryEntryCount); + long dataSize = 0L; + for (CentralDirectory entry : cdList) { + if (!(entry.isCodeFile() && entry.isUncompressed())) { + // if the first file is not uncompressed abc or so, set dataSize to zero + if (entry.getRelativeOffsetOfLocalHeader() == 0) { + dataSize = 0; + break; + } + // the first entry which is not abc/so/an is found, return its data offset + dataSize = entry.getRelativeOffsetOfLocalHeader() + JarFile.LOCHDR + entry.getFileNameLength() + + entry.getExtraFiledLength(); + break; + } + } + if ((dataSize % CodeSignBlock.PAGE_SIZE_4K) != 0) { + throw new HapFormatException( + String.format(Locale.ROOT, "Invalid dataSize(%d), not a multiple of 4096", dataSize)); + } + return dataSize; + } + + private void signNativeLibs(File input) throws IOException, FsVerityDigestException, CodeSignException { + // 'an' libs are always signed + extractedNativeLibSuffixs.add(NATIVE_LIB_AN_SUFFIX); + if (HapUtils.checkCompressNativeLibs(input)) { + LOGGER.info("compressNativeLibs equals true, sign so libs as well."); + // sign so libs only if compressNativeLibs equals true + extractedNativeLibSuffixs.add(NATIVE_LIB_SO_SUFFIX); + } + + // sign native files + JarFile inputJar = new JarFile(input, false); + List entryNames = getNativeEntriesFromHap(inputJar); + if (entryNames.isEmpty()) { + LOGGER.info("No native libs."); + return; + } + List> nativeLivInfoList = signFilesFromJar(entryNames, inputJar); + // update SoInfoSegment in CodeSignBlock + this.codeSignBlock.getSoInfoSegment().setSoInfoList(nativeLivInfoList); + } + + /** + * 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 tess 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 form 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 fsvTessOffset 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 fsvTessOffset) throws FsVerityDigestException, CodeSignException { + FsVerityGenerator fsVerityGenerator = new FsVerityGenerator(); + fsVerityGenerator.generateFsVerityDigest(inputStream, fileSize, fsvTessOffset); + 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, fsvTessOffset, + 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.generateSignData(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[] byteaBeforeCompressionMethod = new byte[CentralDirectory.BYTE_SIZE_BEFORE_COMPRESSION_METHOD]; + cdBuffer.get(byteaBeforeCompressionMethod); + char compressionMode = cdBuffer.getChar(); + 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(); + 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 = new CentralDirectory(compressionMode, fileNameLength, extraFieldLength, + fileCommentLength, locHdrOffset, fileNameBuffer); + cdList.add(cd); + } + + return cdList; + } + + private void printErrorLog(Exception exception) { + if (exception != null) { + LOGGER.error("Code signing error: {}", exception.getMessage(), exception); + } + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/sign/SignedDataGenerator.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/sign/SignedDataGenerator.java new file mode 100644 index 00000000..bfc848d3 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/sign/SignedDataGenerator.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2022 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 + */ +public interface SignedDataGenerator { + /** + * Create a BcSignedDataGenerator instance + */ + SignedDataGenerator BC = new BcSignedDataGenerator(); + + /** + * Generate signature data with specific content and sign configuration + * + * @param content unsigned dile digest content. + * @param signerConfig sign configurations + * @return signed data. + * @throws CodeSignException if error. + */ + byte[] generateSignData(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 00000000..56938ca1 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/sign/VerifyCodeSignature.java @@ -0,0 +1,311 @@ +/* + * Copyright (c) 2022 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); + + public static final String NATIVE_LIB_AN_SUFFIX = ".an"; + + public 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_FROM.contains(fileFormat)) { + LOGGER.info("Not elf file, skip code signing verify"); + return true; + } + // 1) parse sign block to ElfCodeSignBlock object + ElfSignBlock elfSignBlock; + try (FileInputStream signedElf = new FileInputStream(file)) { + byte[] codeSignBlockBytes = new byte[(int) length]; + signedElf.skip(offset); + signedElf.read(codeSignBlockBytes); + elfSignBlock = ElfSignBlock.fromByteArray(codeSignBlockBytes, offset); + } + // 2) verify file data + try (FileInputStream signedElf = new FileInputStream(file)) { + verifySingleFile(signedElf, elfSignBlock.getDataSize(), elfSignBlock.getSignature(), + elfSignBlock.getTreeOffset(), elfSignBlock.getMerkleTreeData()); + } + return true; + } + + /** + * Verify a signed hap's signature + * + * @param file signed hap file + * @param offset start position of code sign block based on the start of the hap file + * @param length byte size of code sign block + * @param fileFormat hap or hqf or hsp, etc. + * @return true if signature verify succeed and false otherwise + * @throws IOException If an input or output exception occurred + * @throws VerifyCodeSignException parsing result invalid + * @throws FsVerityDigestException if fs-verity digest generation failed + * @throws CMSException if signature verify failed + */ + public static boolean verifyHap(File file, long offset, long length, String fileFormat) + throws IOException, VerifyCodeSignException, FsVerityDigestException, CMSException { + if (!CodeSigning.SUPPORT_FILE_FROM.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 = generateFsVerifyDigest(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 generateFsVerifyDigest(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 00000000..a9e9ba56 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/utils/CmsUtils.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2022 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 00000000..2b7a3904 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/utils/DigestUtils.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2022 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 00000000..41a4240f --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/utils/HapUtils.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2022 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 00000000..98e4c2fe --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/utils/InputStreamUtils.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2022 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 in 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; + } +} -- Gitee From 1b733d4f0a12dca2a727b82fff7ccfd45cef71b0 Mon Sep 17 00:00:00 2001 From: yangmingming Date: Thu, 26 Oct 2023 16:24:48 +0800 Subject: [PATCH 05/15] =?UTF-8?q?=E5=BC=80=E6=BA=90=E9=B8=BF=E8=92=99?= =?UTF-8?q?=E7=AD=BE=E5=90=8D=E5=B7=A5=E5=85=B7hap=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E7=AD=BE=E5=90=8D=E4=B8=8ESDK=E4=BB=A3=E7=A0=81=E8=81=94?= =?UTF-8?q?=E8=B0=83=EF=BC=8C=E6=8E=A5=E5=8F=A3=E5=8F=82=E6=95=B0=E5=8F=8A?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=E6=96=B9=E6=B3=95=E8=AF=B4=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: yangmingming --- README.md | 3 +- README_ZH.md | 6 ++- .../hap_sign_tool/src/main/resources/help.txt | 2 +- .../hap/provider/SignProvider.java | 27 +++++++----- .../hapsigntool/hap/verify/VerifyHap.java | 44 ++++++++++++------- .../hapsigntool/hap/verify/VerifyResult.java | 5 +++ 6 files changed, 56 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 30b99052..314e0f59 100644 --- a/README.md +++ b/README.md @@ -103,7 +103,7 @@ The parameters in the command are described as follows: ├── -keystoreFile # KeyStore (KS) file, in JKS or P12 format. It is mandatory if the signing mode is localSign. ├── -keystorePwd # KS password. It is optional. ├── -outFile # Signed HAP file to generate. It is mandatory. - ├── -codesign # Signed HAP file to code sign, "0" do not code sign, "1" do not code sign, default "1"; + ├── -codesign # Signed HAP file to code sign, The value 1 means code signed, and value 0 means code unsigned. The default value is 1. It is optional. 2. One-click signature @@ -277,6 +277,7 @@ Procedure: ├── -keystoreFile # KS file, in JKS or P12 format. It is mandatory if the signing mode is localSign. ├── -keystorePwd # KS password. It is optional. ├── -outFile # Signed HAP file to generate. It is mandatory. + ├── -codesign # Signed HAP file to code sign, The value 1 means code signed, and value 0 means code unsigned. The default value is 1. It is optional. 10.Verify the HAP Signature. diff --git a/README_ZH.md b/README_ZH.md index 5ce50a28..2be1bfc2 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -96,7 +96,7 @@ java -jar hap-sign-tool.jar sign-profile -keyAlias "oh-profile1-key-v1" -signAl ```shell -java -jar hap-sign-tool.jar sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "result\app1.pem" -profileFile "result\app1-profile.p7b" -inFile "app1-unsigned.zip" -keystoreFile "result\ohtest.jks" -outFile "result\app1-unsigned.hap" -keyPwd "123456" -keystorePwd "123456" +java -jar hap-sign-tool.jar sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "result\app1.pem" -profileFile "result\app1-profile.p7b" -inFile "app1-unsigned.zip" -keystoreFile "result\ohtest.jks" -outFile "result\app1-unsigned.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "1" ``` 该命令的参数说明如下: @@ -113,7 +113,8 @@ java -jar hap-sign-tool.jar sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256 ├── -keystoreFile #密钥库文件,localSign模式时为必填项,JKS或P12格式 ├── -keystorePwd #密钥库口令,可选项 ├── -outFile #输出签名后的包文件,必填项 - + ├── -codesign #指示包文件是否带有代码签名,1表示有代码签名,0表示没有代码签名,默认1。可选项 + 2.一键签名 @@ -286,6 +287,7 @@ java -jar hap-sign-tool.jar sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256 ├── -keystoreFile # 密钥库文件,localSign模式时为必填项,JKS或P12格式 ├── -keystorePwd # 密钥库口令,可选项 ├── -outFile # 输出签名后的包文件,必填项 + ├── -codesign # 指示包文件是否带有代码签名,1表示有代码签名,0表示没有代码签名,默认1。可选项 10.hap应用包文件验签 diff --git a/hapsigntool/hap_sign_tool/src/main/resources/help.txt b/hapsigntool/hap_sign_tool/src/main/resources/help.txt index 735df6a0..0aee6ffe 100644 --- a/hapsigntool/hap_sign_tool/src/main/resources/help.txt +++ b/hapsigntool/hap_sign_tool/src/main/resources/help.txt @@ -183,7 +183,7 @@ USAGE: [options] -username : user account for online auth, required fields on remoteSign mode with account auth mode; -userPwd : user password for online auth, required fields on remoteSign mode with account auth mode; -ext : extend parameters for remote signer plugin, optional fields; - -codesign : 0 do not code sign, 1 do not code sign, default 1; + -codesign : 0 do not code sign, 1 do code sign, default 1; EXAMPLE: sign-app -mode localSign -keyAlias "oh-app1-key-v1" -appCertFile "D:\OH\app-release-cert.cer" -profileFile "D:\OH\signed-profile.p7b" -inFile "D:\OH\app1-unsigned.hap" -signAlg SHA256withECDSA -keystoreFile "D:\OH\app-keypair.jks" -keystorePwd ****** -outFile "D:\OH\app1-signed.hap -compatibleVersion 8" diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/provider/SignProvider.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/provider/SignProvider.java index e5a09994..15656d66 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 @@ -20,6 +20,9 @@ import com.google.gson.JsonObject; import com.google.gson.JsonParseException; import com.google.gson.JsonParser; import com.ohos.hapsigntool.api.model.Options; +import com.ohos.hapsigntool.codesigning.exception.CodeSignException; +import com.ohos.hapsigntool.codesigning.exception.FsVerityDigestException; +import com.ohos.hapsigntool.codesigning.sign.CodeSigning; import com.ohos.hapsigntool.error.CustomException; import com.ohos.hapsigntool.hap.config.SignerConfig; import com.ohos.hapsigntool.hap.entity.SigningBlock; @@ -69,7 +72,6 @@ import java.security.cert.CertificateException; import java.security.cert.X509CRL; import java.security.cert.X509Certificate; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -319,7 +321,8 @@ public abstract class SignProvider { isRet = true; } } catch (IOException | InvalidKeyException | HapFormatException | MissingParamsException - | InvalidParamsException | ProfileException | NumberFormatException | CustomException e) { + | InvalidParamsException | ProfileException | NumberFormatException | CustomException + | FsVerityDigestException | CodeSignException e) { printErrorLogWithoutStack(e); } catch (SignatureException e) { printErrorLog(e); @@ -334,15 +337,21 @@ public abstract class SignProvider { * @param tmpOutput temp output file * @param suffix suffix * @param centralDirectoryOffset central directory offset + * @throws FsVerityDigestException FsVerity digest on error + * @throws CodeSignException code sign on error + * @throws IOException IO error + * @throws HapFormatException hap format on error */ - private void appendCodeSignBlock(SignerConfig signerConfig, File tmpOutput, - String suffix, long centralDirectoryOffset) { + private void appendCodeSignBlock(SignerConfig signerConfig, File tmpOutput, String suffix, + long centralDirectoryOffset) + throws FsVerityDigestException, CodeSignException, IOException, HapFormatException { if (signParams.get(ParamConstants.PARAM_CODE_SIGN) .equals(ParamConstants.CodeSignFlag.CODE_SIGNED.getCodeSignFlag())){ - // int codeSignOffset = (int) (centralDirectoryOffset + ((4 + 4 + 4) * (optionalBlocks.size() + 2) + (4 + 4 + 4))); - byte[] codeSignArray=getCodeSignBlock(tmpOutput,codeSignOffset,suffix); + // create CodeSigning Object + CodeSigning codeSigning = new CodeSigning(signerConfig); + byte[] codeSignArray=codeSigning.getCodeSignBlock(tmpOutput,codeSignOffset,suffix); ByteBuffer result = ByteBuffer.allocate(codeSignArray.length + (4 + 4 + 4)); result.order(ByteOrder.LITTLE_ENDIAN); result.putInt(HapUtils.HAP_CODE_SIGN_BLOCK_ID); // type @@ -354,12 +363,6 @@ public abstract class SignProvider { } } - private byte[] getCodeSignBlock(File tmpOutput, int codeSignOffset, String suffix) { - byte[] byteArray = new byte[256]; - Arrays.fill(byteArray, (byte)'1'); - return byteArray; - } - /** * obtain file name suffix * diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/verify/VerifyHap.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/verify/VerifyHap.java index ec3bf4e6..9ca9fb70 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/verify/VerifyHap.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/verify/VerifyHap.java @@ -16,6 +16,9 @@ package com.ohos.hapsigntool.hap.verify; import com.ohos.hapsigntool.api.model.Options; +import com.ohos.hapsigntool.codesigning.exception.FsVerityDigestException; +import com.ohos.hapsigntool.codesigning.exception.VerifyCodeSignException; +import com.ohos.hapsigntool.codesigning.sign.VerifyCodeSignature; import com.ohos.hapsigntool.hap.entity.Pair; import com.ohos.hapsigntool.hap.entity.SigningBlock; import com.ohos.hapsigntool.hap.exception.HapFormatException; @@ -32,6 +35,7 @@ import com.ohos.hapsigntool.zip.ZipUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.bouncycastle.cms.CMSException; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.openssl.jcajce.JcaPEMWriter; import org.bouncycastle.util.Arrays; @@ -250,9 +254,9 @@ public class VerifyHap { ByteBuffer signatureSchemeBlock = blockPair.getFirst(); List optionalBlocks = blockPair.getSecond(); Collections.reverse(optionalBlocks); - if(!checkCodeSign(hapFilePath,optionalBlocks)){ - String errMsg="ZIP64 code sign data error"; - return new VerifyResult(false,VerifyResult.RET_CODESIGN_DATA_ERROR,errMsg); + if (!checkCodeSign(hapFilePath, optionalBlocks)) { + String errMsg = "ZIP64 code sign data error"; + return new VerifyResult(false, VerifyResult.RET_CODESIGN_DATA_ERROR, errMsg); } long signingBlockOffset = hapSigningBlockAndOffsetInFile.getOffset(); @@ -276,6 +280,15 @@ public class VerifyHap { } catch (HapFormatException e) { LOGGER.error("Verify Hap failed, unsupported format hap.", e); result = new VerifyResult(false, VerifyResult.RET_UNSUPPORTED_FORMAT_ERROR, e.getMessage()); + } catch (FsVerityDigestException e) { + LOGGER.error("Verify Hap failed, fs-verity digest generate failed.", e); + result = new VerifyResult(false, VerifyResult.RET_DIGEST_ERROR, e.getMessage()); + } catch (VerifyCodeSignException e) { + LOGGER.error("Verify Hap failed, code sign block verify failed.", e); + result = new VerifyResult(false, VerifyResult.RET_CODE_SIGN_BLOCK_ERROR, e.getMessage()); + } catch (CMSException e) { + LOGGER.error("Verify Hap failed, code signature verify failed.", e); + result = new VerifyResult(false, VerifyResult.RET_SIGNATURE_ERROR, e.getMessage()); } return result; } @@ -285,31 +298,36 @@ public class VerifyHap { * * @param hapFilePath hap file path * @param optionalBlocks optional blocks + * @throws FsVerityDigestException FsVerity digest on error + * @throws IOException IO error + * @throws VerifyCodeSignException verify code sign on error + * @throws CMSException cms on error * @return true or false */ - private boolean checkCodeSign(String hapFilePath, List optionalBlocks) { + private boolean checkCodeSign(String hapFilePath, List optionalBlocks) + throws FsVerityDigestException, IOException, VerifyCodeSignException, CMSException { Map map = optionalBlocks.stream() .collect(Collectors.toMap(SigningBlock::getType, SigningBlock::getValue)); byte[] propertyBlockArray = map.get(HapUtils.HAP_PROPERTY_BLOCK_ID); - if (propertyBlockArray!=null&& propertyBlockArray.length>0){ + if (propertyBlockArray != null && propertyBlockArray.length > 0) { String[] fileNameArray = hapFilePath.split("\\."); - if (fileNameArray.length> getHapSignatureSchemeBlockAndOptionalBlocks(ByteBuffer hapSigningBlock) throws SignatureNotFoundException { try { diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/verify/VerifyResult.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/verify/VerifyResult.java index 58197ade..44049356 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/verify/VerifyResult.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/verify/VerifyResult.java @@ -95,6 +95,11 @@ public class VerifyResult { */ public static final int RET_CODESIGN_DATA_ERROR = 10012; + /** + * Return code of verify code sign error. + */ + public static final int RET_CODE_SIGN_BLOCK_ERROR = 10013; + private boolean result; private int code; private String message; -- Gitee From f13e2058bebf6ba0cd409f26614af29e600872a6 Mon Sep 17 00:00:00 2001 From: yangmingming Date: Fri, 27 Oct 2023 16:04:06 +0800 Subject: [PATCH 06/15] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E9=AA=8C=E7=AD=BEhap?= =?UTF-8?q?=E5=8C=85bug=E5=8F=8A=E6=B7=BB=E5=8A=A0=E5=91=BD=E4=BB=A4verify?= =?UTF-8?q?-app=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: yangmingming --- .../com/ohos/hapsigntool/hap/verify/VerifyHap.java | 2 +- tools/commands.config | 12 ++++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/verify/VerifyHap.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/verify/VerifyHap.java index 9ca9fb70..aad7031c 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/verify/VerifyHap.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/verify/VerifyHap.java @@ -317,7 +317,7 @@ public class VerifyHap { } ByteBuffer byteBuffer = ByteBuffer.wrap(propertyBlockArray); String suffix = fileNameArray[fileNameArray.length - 1]; - ByteBuffer header = HapUtils.reverseSliceBuffer(byteBuffer, 0, ZIP_HEAD_OF_SIGNING_BLOCK_LENGTH); + ByteBuffer header = HapUtils.reverseSliceBuffer(byteBuffer, 0, ZIP_HEAD_OF_SUBSIGNING_BLOCK_LENGTH); int blockOffset = header.getInt(); int blockLength = header.getInt(); int blockType = header.getInt(); diff --git a/tools/commands.config b/tools/commands.config index e15ced48..bc116ea9 100644 --- a/tools/commands.config +++ b/tools/commands.config @@ -88,6 +88,7 @@ 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456"', 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "0"', 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -codesign "1"', + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app2.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_nopass.jks" -outFile "app1-signed.hap" -inForm "zip" -profileSigned "1" -extCfgFile "111.txt"', 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app2.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_nopass.jks" -outFile "app1-signed.hap" -inForm "zip" -profileSigned "1" -extCfgFile "111.txt" -codesign "0"', 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app2.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_nopass.jks" -outFile "app1-signed.hap" -inForm "zip" -profileSigned "1" -extCfgFile "111.txt" -codesign "1"', @@ -99,7 +100,10 @@ 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -inForm "bin" -codesign "1"', 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "profile.json" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -profileSigned "0"', 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "profile.json" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -profileSigned "0" -codesign "0"', - 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "profile.json" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -profileSigned "0" -codesign "1"' + 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app1.pem" -profileFile "profile.json" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_pass.jks" -outFile "app1-signed.hap" -keyPwd "123456" -keystorePwd "123456" -profileSigned "0" -codesign "1"', + 'verify-app -inFile "app1-signed.hap" -outCertChain "app-sign-srv-ca1.cer" -outProfile "app1-profile.p7b"', + 'verify-app -inFile "app1-signed.hap" -outCertChain "app-sign-srv-ca1.cer" -outProfile "app1-profile1.p7b"', + 'verify-app -inFile "app1-signed.hap" -outCertChain "app1.cer" -outProfile "app1-profile.p7b"', ], 'case-assert-false': [ 'generate-keypair -keyPwd 123456 -keyAlg ECC -keySize NIST-P-384 -keystoreFile "ohtest.jks" -keystorePwd 123456 -extCfgFile "111.txt"', @@ -464,6 +468,10 @@ 'sign-app -keyAlias "oh-app1-key-v1" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app2.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_nopass.jks" -outFile "app1-signed.hap" -inForm -profileSigned -extCfgFile -codesign "1"', 'sign-app -keyAlias "oh-app1-key-v1" -keyPwd "123456" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app2.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_nopass.jks" -outFile "app1-signed.hap" -inForm -profileSigned -extCfgFile ', 'sign-app -keyAlias "oh-app1-key-v1" -keyPwd "123456" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app2.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_nopass.jks" -outFile "app1-signed.hap" -inForm -profileSigned -extCfgFile -codesign "0"', - 'sign-app -keyAlias "oh-app1-key-v1" -keyPwd "123456" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app2.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_nopass.jks" -outFile "app1-signed.hap" -inForm -profileSigned -extCfgFile -codesign "1"' + 'sign-app -keyAlias "oh-app1-key-v1" -keyPwd "123456" -signAlg "SHA256withECDSA" -mode "localSign" -appCertFile "app2.pem" -profileFile "app1-profile.p7b" -inFile "test/app1-unsigned.hap" -keystoreFile "ohtest_nopass.jks" -outFile "app1-signed.hap" -inForm -profileSigned -extCfgFile -codesign "1"', + 'verify-app -inFile "app1-signed.hap" -outCertChain "" -outProfile "app1-profile.p7b"', + 'verify-app -inFile "app1-signed.hap" -outCertChain "app1.cer" -outProfile ""', + 'verify-app -inFile "app1-signed.hap" -outCertChain "" -outProfile ""', + 'verify-app -inFile "app1-signed.hap" -outCertChain "app1.ce7778r" -outProfile "app1-profile1.p7b"', ] } \ No newline at end of file -- Gitee From edca8a73c9e881244854fb1d0a94715e036384f4 Mon Sep 17 00:00:00 2001 From: zfeixiang Date: Tue, 24 Oct 2023 22:10:01 +0800 Subject: [PATCH 07/15] =?UTF-8?q?=E5=BA=94=E7=94=A8=E5=8F=8A=E4=BA=8C?= =?UTF-8?q?=E8=BF=9B=E5=88=B6=E6=96=87=E4=BB=B6=E4=BB=A3=E7=A0=81=E7=AD=BE?= =?UTF-8?q?=E5=90=8D=E8=83=BD=E5=8A=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: zfeixiang --- .../datastructure/CodeSignBlock.java | 270 ++++++++++ .../datastructure/CodeSignBlockHeader.java | 194 +++++++ .../datastructure/ElfSignBlock.java | 473 ++++++++++++++++++ .../codesigning/datastructure/Extension.java | 76 +++ .../datastructure/FsVerityInfoSegment.java | 155 ++++++ .../datastructure/HapInfoSegment.java | 139 +++++ .../datastructure/MerkleTreeExtension.java | 144 ++++++ .../datastructure/NativeLibInfoSegment.java | 283 +++++++++++ .../datastructure/SegmentHeader.java | 156 ++++++ .../codesigning/datastructure/SignInfo.java | 452 +++++++++++++++++ .../datastructure/SignedFilePos.java | 123 +++++ .../exception/CodeSignException.java | 44 ++ .../exception/FsVerityDigestException.java | 33 ++ .../exception/VerifyCodeSignException.java | 45 ++ .../fsverity/FsVerityDescriptor.java | 120 +++++ .../codesigning/fsverity/FsVerityDigest.java | 52 ++ .../fsverity/FsVerityGenerator.java | 158 ++++++ .../fsverity/FsVerityHashAlgorithm.java | 50 ++ .../codesigning/fsverity/MerkleTree.java | 44 ++ .../fsverity/MerkleTreeBuilder.java | 354 +++++++++++++ .../sign/BcSignedDataGenerator.java | 246 +++++++++ .../codesigning/sign/CentralDirectory.java | 119 +++++ .../codesigning/sign/CodeSigning.java | 409 +++++++++++++++ .../codesigning/sign/SignedDataGenerator.java | 42 ++ .../codesigning/sign/VerifyCodeSignature.java | 311 ++++++++++++ .../codesigning/utils/CmsUtils.java | 86 ++++ .../codesigning/utils/DigestUtils.java | 40 ++ .../codesigning/utils/HapUtils.java | 105 ++++ .../codesigning/utils/InputStreamUtils.java | 64 +++ 29 files changed, 4787 insertions(+) create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/CodeSignBlock.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/CodeSignBlockHeader.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/ElfSignBlock.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/Extension.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/FsVerityInfoSegment.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/HapInfoSegment.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/MerkleTreeExtension.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/NativeLibInfoSegment.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/SegmentHeader.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/SignInfo.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/SignedFilePos.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/exception/CodeSignException.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/exception/FsVerityDigestException.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/exception/VerifyCodeSignException.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/fsverity/FsVerityDescriptor.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/fsverity/FsVerityDigest.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/fsverity/FsVerityGenerator.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/fsverity/FsVerityHashAlgorithm.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/fsverity/MerkleTree.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/fsverity/MerkleTreeBuilder.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/sign/BcSignedDataGenerator.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/sign/CentralDirectory.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/sign/CodeSigning.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/sign/SignedDataGenerator.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/sign/VerifyCodeSignature.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/utils/CmsUtils.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/utils/DigestUtils.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/utils/HapUtils.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/utils/InputStreamUtils.java diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/CodeSignBlock.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/CodeSignBlock.java new file mode 100644 index 00000000..d253bfe6 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/CodeSignBlock.java @@ -0,0 +1,270 @@ +/* + * Copyright (c) 2023-2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ohos.hapsigntool.codesigning.datastructure; + +import com.ohos.hapsigntool.codesigning.sign.CodeSigning; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +/** + * code sign block is a chunk of bytes attached to hap package or file. + * It consists of two headers: + * 1) code sign block header + * 2) segment header + * three segments: + * 1) fs-verity info segment + * 2) hap info segment + * 3) so info segment + * one zero padding area in order to align merkle tree raw bytes + * 1) zero padding + * and one area storing merkle tree bytes: + * 1) merkle tree raw bytes + *

+ * After signing a hap, call toByteArray() method to generate a block of bytes. + * + * @since 2023/09/08 + */ +public class CodeSignBlock { + /** + * page size in bytes + */ + public static final long PAGE_SIZE_4K = 4096L; + + /** + * Segment header count, including fs-verity info, hap info, so info segment + */ + public static final int SEGMENT_HEADER_COUNT = 3; + + private CodeSignBlockHeader codeSignBlockHeader; + + private final List segmentHeaderList; + + private FsVerityInfoSegment fsVerityInfoSegment; + + private HapInfoSegment hapInfoSegment; + + private NativeLibInfoSegment nativeLibInfoSegment; + + private byte[] zeroPadding; + + private final Map merkleTreeMap; + + public CodeSignBlock() { + this.codeSignBlockHeader = new CodeSignBlockHeader(); + this.segmentHeaderList = new ArrayList<>(); + this.fsVerityInfoSegment = new FsVerityInfoSegment(); + this.hapInfoSegment = new HapInfoSegment(); + this.nativeLibInfoSegment = new NativeLibInfoSegment(); + this.merkleTreeMap = new HashMap<>(); + } + + /** + * Add one merkle tree into merkleTreeMap + * + * @param key file name + * @param merkleTree merkle tree raw bytes + */ + public void addOneMerkleTree(String key, byte[] merkleTree) { + if (merkleTree == null) { + this.merkleTreeMap.put(key, new byte[0]); + } else { + this.merkleTreeMap.put(key, merkleTree); + } + } + + /** + * Get one merkle tree from merkleTreeMap by file name + * + * @param key file name + * @return merkle tree bytes + */ + public byte[] getOneMerkleTreeByFileName(String key) { + return this.merkleTreeMap.get(key); + } + + /** + * set code sign block flag + */ + public void setCodeSignBlockFlag() { + int flags = CodeSignBlockHeader.FLAG_MERKLE_TREE_INLINED; + if (this.nativeLibInfoSegment.getSectionNum() != 0) { + flags += CodeSignBlockHeader.FLAG_NATIVE_LIB_INCLUDED; + } + this.codeSignBlockHeader.setFlags(flags); + } + + /** + * set segmentNum defined in code sign block header, equals length of segmentHeaderList + */ + public void setSegmentNum() { + this.codeSignBlockHeader.setSegmentNum(segmentHeaderList.size()); + } + + /** + * add one segment to segmentHeaderList + * + * @param sh segment header + */ + public void addToSegmentList(SegmentHeader sh) { + this.segmentHeaderList.add(sh); + } + + public List getSegmentHeaderList() { + return segmentHeaderList; + } + + /** + * set segment header list + */ + public void setSegmentHeaders() { + // fs-verity info segment + segmentHeaderList.add(new SegmentHeader(SegmentHeader.CSB_FSVERITY_INFO_SEG, this.fsVerityInfoSegment.size())); + // hap info segment + segmentHeaderList.add(new SegmentHeader(SegmentHeader.CSB_HAP_META_SEG, this.hapInfoSegment.size())); + // native lib info segment + segmentHeaderList.add( + new SegmentHeader(SegmentHeader.CSB_NATIVE_LIB_INFO_SEG, this.nativeLibInfoSegment.size())); + } + + public CodeSignBlockHeader getCodeSignBlockHeader() { + return codeSignBlockHeader; + } + + public void setCodeSignBlockHeader(CodeSignBlockHeader csbHeader) { + this.codeSignBlockHeader = csbHeader; + } + + public void setFsVerityInfoSegment(FsVerityInfoSegment fsVeritySeg) { + this.fsVerityInfoSegment = fsVeritySeg; + } + + public FsVerityInfoSegment getFsVerityInfoSegment() { + return fsVerityInfoSegment; + } + + public HapInfoSegment getHapInfoSegment() { + return hapInfoSegment; + } + + public void setHapInfoSegment(HapInfoSegment hapSeg) { + this.hapInfoSegment = hapSeg; + } + + public NativeLibInfoSegment getSoInfoSegment() { + return nativeLibInfoSegment; + } + + public void setSoInfoSegment(NativeLibInfoSegment soSeg) { + this.nativeLibInfoSegment = soSeg; + } + + /** + * Convert code sign block object to a newly created byte array + * + * @return Byte array representation of a CodeSignBlock object + */ + public byte[] toByteArray() { + ByteBuffer bf = ByteBuffer.allocate(this.codeSignBlockHeader.getBlockSize()).order(ByteOrder.LITTLE_ENDIAN); + bf.put(this.codeSignBlockHeader.toByteArray()); + for (SegmentHeader sh : this.segmentHeaderList) { + bf.put(sh.toByteArray()); + } + bf.put(this.zeroPadding); + // Hap merkle tree + if (this.hapInfoSegment.getSignInfo().getExtensionByType(MerkleTreeExtension.MERKLE_TREE_INLINED) != null) { + bf.put(merkleTreeMap.get("Hap")); + } + bf.put(this.fsVerityInfoSegment.toByteArray()); + bf.put(this.hapInfoSegment.toByteArray()); + bf.put(this.nativeLibInfoSegment.toByteArray()); + return bf.array(); + } + + /** + * SegmentOffset is the position of each segment defined in segmentHeaderList, + * based on the start position of code sign block + */ + public void computeSegmentOffset() { + // 1) the first segment is placed after merkle tree + int segmentOffset = CodeSignBlockHeader.size() + + this.segmentHeaderList.size() * SegmentHeader.SEGMENT_HEADER_LENGTH + this.zeroPadding.length + + this.getOneMerkleTreeByFileName(CodeSigning.HAP_SIGNATURE_ENTRY_NAME).length; + for (SegmentHeader sh : segmentHeaderList) { + sh.setSegmentOffset(segmentOffset); + segmentOffset += sh.getSegmentSize(); + } + } + + /** + * Compute the offset to store merkle tree raw bytes based on file start + * + * @param codeSignBlockOffset offset to store code sign block based on file start + * @return offset to store merkle tree based on the file start, it includes the codeSignBlockOffset + */ + public long computeMerkleTreeOffset(long codeSignBlockOffset) { + long sizeWithoutMerkleTree = CodeSignBlockHeader.size() + + SEGMENT_HEADER_COUNT * SegmentHeader.SEGMENT_HEADER_LENGTH; + // add code sign block offset while computing align position for merkle tree + long residual = (codeSignBlockOffset + sizeWithoutMerkleTree) % PAGE_SIZE_4K; + if (residual == 0) { + this.zeroPadding = new byte[0]; + } else { + this.zeroPadding = new byte[(int) (PAGE_SIZE_4K - residual)]; + } + return codeSignBlockOffset + sizeWithoutMerkleTree + zeroPadding.length; + } + + /** + * Convert CodeSignBlock to bytes + * + * @param fsvTreeOffset merkle tree offset + * @return byte array representing the code sign block + */ + public byte[] generateCodeSignBlockByte(long fsvTreeOffset) { + // 1) compute overall block size without merkle tree + long csbSize = CodeSignBlockHeader.size() + + (long) this.segmentHeaderList.size() * SegmentHeader.SEGMENT_HEADER_LENGTH + this.zeroPadding.length + + this.getOneMerkleTreeByFileName(CodeSigning.HAP_SIGNATURE_ENTRY_NAME).length + + this.fsVerityInfoSegment.size() + this.hapInfoSegment.size() + this.nativeLibInfoSegment.size(); + Extension ext = this.hapInfoSegment.getSignInfo().getExtensionByType(MerkleTreeExtension.MERKLE_TREE_INLINED); + if (ext instanceof MerkleTreeExtension) { + MerkleTreeExtension merkleTreeExtension = (MerkleTreeExtension) ext; + merkleTreeExtension.setMerkleTreeOffset(fsvTreeOffset); + } + this.codeSignBlockHeader.setBlockSize(csbSize); + // 2) generate byte array of complete code sign block + return toByteArray(); + } + + /** + * Return a string representation of the object + * + * @return string representation of the object + */ + public String toString() { + return String.format(Locale.ROOT, + "CodeSignBlockHeader[%s], SegmentHeaders[%s], FsVeritySeg[%s], HapInfoSeg[%s], SoInfoSeg[%s]", + this.codeSignBlockHeader, Arrays.toString(this.segmentHeaderList.toArray()), this.fsVerityInfoSegment, + this.hapInfoSegment, this.nativeLibInfoSegment); + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/CodeSignBlockHeader.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/CodeSignBlockHeader.java new file mode 100644 index 00000000..6848b2e4 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/CodeSignBlockHeader.java @@ -0,0 +1,194 @@ +/* + * Copyright (c) 2023-2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ohos.hapsigntool.codesigning.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 = 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]; + + /** + * Default construct of CodeSignBlockHeader + */ + public CodeSignBlockHeader() { + } + + /** + * Construct of CodeSignBlockHeader + * + * @param magic magic number + * @param version version + * @param blockSize code sign block size + * @param segmentNum segment number + * @param flags flags + * @param reserved reserved + */ + public CodeSignBlockHeader(long magic, int version, int blockSize, int segmentNum, int flags, byte[] reserved) { + this.magic = magic; + this.version = version; + this.blockSize = blockSize; + this.segmentNum = segmentNum; + this.flags = flags; + this.reserved = 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 CodeSignBlockHeader(inMagic, inVersion, inBlockSize, inSegmentNum, inFlags, inReserved); + } + + /** + * 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); + } +} 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 00000000..80158950 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/ElfSignBlock.java @@ -0,0 +1,473 @@ +/* + * Copyright (c) 2023-2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ohos.hapsigntool.codesigning.datastructure; + +import com.ohos.hapsigntool.codesigning.exception.VerifyCodeSignException; +import com.ohos.hapsigntool.codesigning.fsverity.FsVerityDescriptor; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Arrays; + +/** + * elf sign block is a chunk of bytes attached to elf file. + * 1) u32 type: 0x2 merkle tree + * 2) u32 length: merkle tree with padding size + * 3) u8[] merkle tree data + * 4) u32 type: 0x1 fsverity descriptor + * 5) u32 length: fsverity descriptor size + * 6) u8 version: fs-verity version + * 7) u8 hashAlgorithm: hash algorithm to use for the Merkle tree + * 8) u8 log2BlockSize: log2 of size of data and tree blocks + * 9) u8 saltSize: byte size of salt + * 10) u32 signSize: byte size of signature + * 11) u64 dataSize: byte size of data being signed + * 12) u8[64] rootHash: merkle tree root hash + * 13) u8[32] salt: salt used in signing + * 14) u32 flags + * 15) u32 reserved + * 16) u64 treeOffset: merkle tree offset + * 17) u8[128] reserved + * 18) u8[] 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 static final int ROOT_HASH_SIZE = 64; + + private static final int SALT_BUFFER_LENGTH = 32; + + private static final int RESERVED_BYTE_ARRAY_LENGTH = 128; + + private static final int FSD_WITHOUT_SIGN_LENGTH = 256; + + private int treeType; + + private int treeLength; + + // zero padding before merkle tree + private byte[] paddingBeforeTree; + + private byte[] merkleTreeData; + + private int fsdType; + + private int fsdLength; + + private byte fsVersion; + + private byte fsHashAlgorithm; + + private byte log2BlockSize; + + private byte saltSize; + + private int signSize; + + private long dataSize; + + private byte[] rootHash; + + private byte[] salt; + + private int flags; + + private int reservedInt; + + private long treeOffset; + + private byte[] reservedData; + + private byte[] signature; + + private ElfSignBlock(ElfSignBlockBuilder builder) { + this.treeType = builder.treeType; + this.treeLength = builder.treeLength; + this.paddingBeforeTree = builder.paddingBeforeTree; + this.merkleTreeData = builder.merkleTreeData; + this.fsdType = builder.fsdType; + this.fsdLength = builder.fsdLength; + this.fsVersion = builder.fsVersion; + this.fsHashAlgorithm = builder.fsHashAlgorithm; + this.log2BlockSize = builder.log2BlockSize; + this.saltSize = builder.saltSize; + this.signSize = builder.signSize; + this.dataSize = builder.dataSize; + this.rootHash = builder.rootHash; + this.salt = builder.salt; + this.flags = builder.flags; + this.reservedInt = builder.reservedInt; + this.treeOffset = builder.treeOffset; + this.reservedData = builder.reservedData; + this.signature = builder.signature; + } + + /** + * Return the byte size of code sign block + * + * @return byte size of code sign block + */ + public int size() { + return Integer.BYTES * 2 + paddingBeforeTree.length + merkleTreeData.length + Integer.BYTES * 2 + + FSD_WITHOUT_SIGN_LENGTH + signature.length; + } + + /** + * add fs-verity info + * + * @param version fs-verity version + * @param hashAlgorithm fs-verity hash algorithm id + * @param log2BlockSize log2 of hash block size + */ + public void addFsVerityInfo(byte version, byte hashAlgorithm, byte log2BlockSize) { + this.fsVersion = version; + this.fsHashAlgorithm = hashAlgorithm; + this.log2BlockSize = log2BlockSize; + } + + /** + * add sign info + * + * @param saltSize byte size of salt + * @param flags reserved flags + * @param fileSize byte size of data being signed + * @param salt salt in byte array representation + * @param signature signature after signing the data in byte array representation + */ + public void addSignInfo(int saltSize, int flags, long fileSize, byte[] salt, byte[] signature) { + this.saltSize = (byte) saltSize; + this.flags = flags; + this.dataSize = fileSize; + if (salt != null) { + this.salt = Arrays.copyOf(salt, SALT_BUFFER_LENGTH); + } + if (signature != null) { + this.signature = signature; + } + this.signSize = this.signature.length; + this.fsdLength = FSD_WITHOUT_SIGN_LENGTH + signSize; + } + + /** + * add merkle tree info + * + * @param merkleTreeBytes merkle tree data + * @param fsvTreeOffset merkle tree offset based on file start + * @param rootHash Root hash of the merkle tree + */ + public void addMerkleTreeInfo(byte[] merkleTreeBytes, long fsvTreeOffset, byte[] rootHash) { + if (merkleTreeBytes != null) { + this.merkleTreeData = merkleTreeBytes; + } + this.treeOffset = fsvTreeOffset; + if (rootHash != null) { + this.rootHash = Arrays.copyOf(rootHash, ROOT_HASH_SIZE); + } + treeLength = paddingBeforeTree.length + merkleTreeData.length; + } + + /** + * add padding length offset while computing align position for merkle tree + * + * @param signBlockOffset sign block offset based on the start of file + * @return merkle tree raw bytes offset based on the start of file + */ + public long computeMerkleTreeOffset(long signBlockOffset) { + long residual = (signBlockOffset + Integer.BYTES * 2) % PAGE_SIZE_4K; + if (residual > 0) { + this.paddingBeforeTree = new byte[(int) (PAGE_SIZE_4K - residual)]; + } + return signBlockOffset + Integer.BYTES * 2 + paddingBeforeTree.length; + } + + public byte[] getMerkleTreeData() { + return merkleTreeData; + } + + public long getDataSize() { + return dataSize; + } + + public long getTreeOffset() { + return treeOffset; + } + + public byte[] getSignature() { + return signature; + } + + /** + * Return a string representation of the object + * + * @return string representation of the object + */ + public byte[] toByteArray() { + ByteBuffer bf = ByteBuffer.allocate(size()).order(ByteOrder.LITTLE_ENDIAN); + bf.putInt(treeType); + bf.putInt(paddingBeforeTree.length + merkleTreeData.length); + bf.put(paddingBeforeTree); + bf.put(merkleTreeData); + bf.putInt(fsdType); + bf.putInt(fsdLength); + bf.put(fsVersion); + bf.put(fsHashAlgorithm); + bf.put(log2BlockSize); + bf.put(saltSize); + bf.putInt(signSize); + bf.putLong(dataSize); + bf.put(rootHash); + bf.put(salt); + bf.putInt(flags); + bf.putInt(reservedInt); + bf.putLong(treeOffset); + bf.put(reservedData); + bf.put(signature); + return bf.array(); + } + + /** + * Init the ElfSignBlock by a byte array + * + * @param bytes Byte array representation of a CodeSignBlockHeader object + * @param signBlockOffset start position of code sign block based on the start of the elf file + * @return a newly created CodeSignBlockHeader object + * @throws VerifyCodeSignException parse result invalid + */ + public static ElfSignBlock fromByteArray(byte[] bytes, long signBlockOffset) throws VerifyCodeSignException { + ByteBuffer bf = ByteBuffer.allocate(bytes.length).order(ByteOrder.LITTLE_ENDIAN); + bf.put(bytes); + // after put, rewind is mandatory before get + bf.rewind(); + ElfSignBlockBuilder builder = new ElfSignBlockBuilder(); + int inTreeType = bf.getInt(); + if (MERKLE_TREE_INLINED != inTreeType) { + throw new VerifyCodeSignException("Invalid merkle tree type of ElfSignBlock"); + } + int inTreeLength = bf.getInt(); + builder.setTreeType(inTreeType).setTreeLength(inTreeLength); + int paddingLength = (int) (PAGE_SIZE_4K - (signBlockOffset + Integer.BYTES * 2) % PAGE_SIZE_4K); + byte[] zeroPadding = new byte[paddingLength]; + bf.get(zeroPadding); + byte[] treeWithoutPadding = new byte[inTreeLength - paddingLength]; + bf.get(treeWithoutPadding); + builder.setPaddingBeforeTree(zeroPadding).setMerkleTreeData(treeWithoutPadding); + int inFsdType = bf.getInt(); + if (FsVerityDescriptor.FS_VERITY_DESCRIPTOR_TYPE != inFsdType) { + throw new VerifyCodeSignException("Invalid fs-verify descriptor type of ElfSignBlock"); + } + int inFsdLength = bf.getInt(); + if (bytes.length != Integer.BYTES * 2 + inTreeLength + Integer.BYTES * 2 + inFsdLength) { + throw new VerifyCodeSignException("Invalid fs-verify descriptor with signature length of ElfSignBlock"); + } + builder.setFsdType(inFsdType).setFsdLength(inFsdLength); + fillFsd(builder, bf, inFsdLength); + return builder.build(); + } + + private static void fillFsd(ElfSignBlockBuilder builder, ByteBuffer bf, int inFsdLength) + throws VerifyCodeSignException { + + byte inFsVersion = bf.get(); + if (FsVerityDescriptor.VERSION != inFsVersion) { + throw new VerifyCodeSignException("Invalid fs-verify descriptor version of ElfSignBlock"); + } + byte inFsHashAlgorithm = bf.get(); + byte inLog2BlockSize = bf.get(); + builder.setFsVersion(inFsVersion).setFsHashAlgorithm(inFsHashAlgorithm).setLog2BlockSize(inLog2BlockSize); + byte inSaltSize = bf.get(); + if (SALT_BUFFER_LENGTH != inSaltSize) { + throw new VerifyCodeSignException("Invalid salt size of ElfSignBlock"); + } + int inSignSize = bf.getInt(); + if (inFsdLength != inSignSize + FSD_WITHOUT_SIGN_LENGTH) { + throw new VerifyCodeSignException("Invalid sign size of ElfSignBlock"); + } + int inDataSize = bf.getInt(); + byte[] inRootHash = new byte[ROOT_HASH_SIZE]; + bf.get(inRootHash); + builder.setSaltSize(inSaltSize).setSignSize(inSignSize).setDataSize(inDataSize).setRootHash(inRootHash); + byte[] inSalt = new byte[inSaltSize]; + bf.get(inSalt); + int inFlags = bf.getInt(); + bf.getInt(); + long inTreeOffset = bf.getLong(); + if (inTreeOffset % PAGE_SIZE_4K != 0) { + throw new VerifyCodeSignException("Invalid merkle tree offset of ElfSignBlock"); + } + bf.get(new byte[RESERVED_BYTE_ARRAY_LENGTH]); + byte[] inSignature = new byte[inSignSize]; + bf.get(inSignature); + builder.setSalt(inSalt).setFlags(inFlags).setTreeOffset(inTreeOffset).setSignature(inSignature); + } + + /** + * Builder of ElfSignBlock class + */ + public static class ElfSignBlockBuilder { + private int treeType = MERKLE_TREE_INLINED; + + private int treeLength; + + // zero padding before merkle tree + private byte[] paddingBeforeTree = new byte[0]; + + private byte[] merkleTreeData = new byte[0]; + + private int fsdType = FsVerityDescriptor.FS_VERITY_DESCRIPTOR_TYPE; + + private int fsdLength = FSD_WITHOUT_SIGN_LENGTH; + + private byte fsVersion = FsVerityDescriptor.VERSION; + + private byte fsHashAlgorithm; + + private byte log2BlockSize; + + private byte saltSize; + + private int signSize; + + private long dataSize; + + private byte[] rootHash = new byte[ROOT_HASH_SIZE]; + + private byte[] salt = new byte[SALT_BUFFER_LENGTH]; + + private int flags; + + private int reservedInt = 0; + + private long treeOffset; + + private byte[] reservedData = new byte[RESERVED_BYTE_ARRAY_LENGTH]; + + private byte[] signature = new byte[0]; + + public ElfSignBlockBuilder setTreeType(int treeType) { + this.treeType = treeType; + return this; + } + + public ElfSignBlockBuilder setTreeLength(int treeLength) { + this.treeLength = treeLength; + return this; + } + + public ElfSignBlockBuilder setPaddingBeforeTree(byte[] paddingBeforeTree) { + this.paddingBeforeTree = paddingBeforeTree; + return this; + } + + public ElfSignBlockBuilder setMerkleTreeData(byte[] merkleTreeData) { + this.merkleTreeData = merkleTreeData; + return this; + } + + public ElfSignBlockBuilder setFsdType(int fsdType) { + this.fsdType = fsdType; + return this; + } + + public ElfSignBlockBuilder setFsdLength(int fsdLength) { + this.fsdLength = fsdLength; + return this; + } + + public ElfSignBlockBuilder setFsVersion(byte fsVersion) { + this.fsVersion = fsVersion; + return this; + } + + public ElfSignBlockBuilder setFsHashAlgorithm(byte fsHashAlgorithm) { + this.fsHashAlgorithm = fsHashAlgorithm; + return this; + } + + public ElfSignBlockBuilder setLog2BlockSize(byte log2BlockSize) { + this.log2BlockSize = log2BlockSize; + return this; + } + + public ElfSignBlockBuilder setSaltSize(byte saltSize) { + this.saltSize = saltSize; + return this; + } + + public ElfSignBlockBuilder setSignSize(int signSize) { + this.signSize = signSize; + return this; + } + + public ElfSignBlockBuilder setDataSize(long dataSize) { + this.dataSize = dataSize; + return this; + } + + public ElfSignBlockBuilder setRootHash(byte[] rootHash) { + this.rootHash = rootHash; + return this; + } + + public ElfSignBlockBuilder setSalt(byte[] salt) { + this.salt = salt; + return this; + } + + public ElfSignBlockBuilder setFlags(int flags) { + this.flags = flags; + return this; + } + + public ElfSignBlockBuilder setReservedInt(int reservedInt) { + this.reservedInt = reservedInt; + return this; + } + + public ElfSignBlockBuilder setTreeOffset(long treeOffset) { + this.treeOffset = treeOffset; + return this; + } + + public ElfSignBlockBuilder setReservedData(byte[] reservedData) { + this.reservedData = reservedData; + return this; + } + + public ElfSignBlockBuilder setSignature(byte[] signature) { + this.signature = signature; + return this; + } + + /** + * return a ElfSignBlock object + * + * @return a ElfSignBlock object + */ + public ElfSignBlock build() { + return new ElfSignBlock(this); + } + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/Extension.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/Extension.java new file mode 100644 index 00000000..2d23c548 --- /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 00000000..117431cf --- /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 00000000..0a25ba25 --- /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 00000000..303d92ed --- /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 00000000..98dd744d --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/NativeLibInfoSegment.java @@ -0,0 +1,283 @@ +/* + * 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 = MAGIC_NUM; + + private int segmentSize; + + private int sectionNum; + + private List> soInfoList = new ArrayList<>(); + + private List signedFilePosList = new ArrayList<>(); + + private List fileNameList = new ArrayList<>(); + + private List signInfoList = new ArrayList<>(); + + private byte[] zeroPadding = new byte[0]; + + private int fileNameListBlockSize; + + private int signInfoListBlockSize; + + /** + * Default constructor + */ + public NativeLibInfoSegment() { + } + + /** + * Constructor for SoInfoSegment by byte array + * + * @param magic byte array representation of SoInfoSegment object + * @param segmentSize byte size of NativeLibInfoSegment + * @param sectionNum number of native libs + * @param signedFilePosList offset and size of each file + * @param fileNameList file name of each signed file + * @param signInfoList sign info of each signed file + * @param zeroPadding zero padding before sign info list + */ + public NativeLibInfoSegment(int magic, int segmentSize, int sectionNum, List signedFilePosList, + List fileNameList, List signInfoList, byte[] zeroPadding) { + this.magic = magic; + this.segmentSize = segmentSize; + this.sectionNum = sectionNum; + this.signedFilePosList = signedFilePosList; + this.fileNameList = fileNameList; + this.signInfoList = signInfoList; + this.zeroPadding = 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 NativeLibInfoSegment(inMagic, inSegmentSize, inSectionNum, inSignedFilePosList, inFileNameList, + inSignInfoList, inZeroPadding); + } + + /** + * 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())); + } +} 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 00000000..975dda21 --- /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 00000000..e2d538c4 --- /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 00000000..6b2eb335 --- /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 00000000..8263bf50 --- /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 00000000..5ac1210b --- /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 00000000..36d28623 --- /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 00000000..d29de6f6 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/fsverity/FsVerityDescriptor.java @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2023-2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ohos.hapsigntool.codesigning.fsverity; + +import com.ohos.hapsigntool.codesigning.exception.FsVerityDigestException; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * Format of FsVerity descriptor + * uint8 version + * uint8 hashAlgorithm + * uint8 log2BlockSize + * uint8 saltSize + * uint8[4] 0 + * le64 dataSize + * uint8[64] rootHash + * uint8[32] salt + * uint32 flags + * uint8[4] 0 + * uint64 treeOffset + * uint8[128] 0 + * + * @since 2023/06/05 + */ +public class FsVerityDescriptor { + /** + * fs-verity version, must be 1 + */ + public static final byte VERSION = 1; + + /** + * Indicating merkle tree offset is set in fs-verity descriptor + */ + public static final int FLAG_STORE_MERKLE_TREE_OFFSET = 0x1; + + /** + * Indicating fs-verity descriptor type + */ + public static final int FS_VERITY_DESCRIPTOR_TYPE = 0x1; + + private static final int DESCRIPTOR_SIZE = 256; + + private static final int ROOT_HASH_FILED_SIZE = 64; + + private static final int SALT_SIZE = 32; + + private static final int FIRST_RESERVED_SIZE = 4; + + private static final int RESERVED_SIZE_AFTER_FLAGS = 4; + + /** + * Get FsVerity descriptor + * + * @param fileSize size of input + * @param hashAlgorithm hash algorithm id + * @param log2BlockSize log2 of hash block size + * @param salt salt used for hash + * @param rawRootHash root hash of merkle tree + * @param flags flag indicating whether merkle tree offset is present in fs-verity descriptor + * @param merkleTreeOffset merkle tree offset based on file start + * @return bytes of descriptor + * @throws FsVerityDigestException if error + */ + public static byte[] getDescriptor(long fileSize, byte hashAlgorithm, byte log2BlockSize, byte[] salt, + byte[] rawRootHash, int flags, long merkleTreeOffset) throws FsVerityDigestException { + ByteBuffer buffer = ByteBuffer.allocate(DESCRIPTOR_SIZE).order(ByteOrder.LITTLE_ENDIAN); + buffer.put(VERSION); + buffer.put(hashAlgorithm); + buffer.put(log2BlockSize); + if (salt == null) { + buffer.put((byte) 0); + } else if (salt.length > SALT_SIZE) { + throw new FsVerityDigestException("Salt is too long"); + } else { + buffer.put((byte) salt.length); + } + writeBytesWithSize(buffer, null, FIRST_RESERVED_SIZE); + buffer.putLong(fileSize); + writeBytesWithSize(buffer, rawRootHash, ROOT_HASH_FILED_SIZE); + writeBytesWithSize(buffer, salt, SALT_SIZE); + buffer.putInt(flags); + writeBytesWithSize(buffer, null, RESERVED_SIZE_AFTER_FLAGS); + buffer.putLong(merkleTreeOffset); + return buffer.array(); + } + + /** + * Write bytes to ByteBuffer with specific size + * + * @param buffer target buffer + * @param src bytes to write + * @param size size of written bytes, fill 0 if src bytes is long enough + */ + private static void writeBytesWithSize(ByteBuffer buffer, byte[] src, int size) { + int pos = buffer.position(); + if (src != null) { + if (src.length > size) { + buffer.put(src, 0, size); + } else { + buffer.put(src); + } + } + buffer.position(pos + size); + } +} diff --git a/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 00000000..cff3617c --- /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 00000000..b46620e6 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/fsverity/FsVerityGenerator.java @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2023-2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ohos.hapsigntool.codesigning.fsverity; + +import com.ohos.hapsigntool.codesigning.exception.FsVerityDigestException; +import com.ohos.hapsigntool.codesigning.utils.DigestUtils; + +import java.io.IOException; +import java.io.InputStream; +import java.security.NoSuchAlgorithmException; + +/** + * FsVerity data generator supper class + * + * @since 2023/06/05 + */ +public class FsVerityGenerator { + /** + * FsVerity hash algorithm + */ + private static final FsVerityHashAlgorithm FS_VERITY_HASH_ALGORITHM = FsVerityHashAlgorithm.SHA256; + + private static final byte LOG_2_OF_FSVERITY_HASH_PAGE_SIZE = 12; + + /** + * salt for hashing one page + */ + protected byte[] salt = null; + + private byte[] fsVerityDigest = null; + + private byte[] treeBytes = null; + + private byte[] rootHash = null; + + /** + * generate merkle tree of given input + * + * @param inputStream input stream for generate merkle tree + * @param size total size of input stream + * @param fsVerityHashAlgorithm hash algorithm for FsVerity + * @return merkle tree + * @throws FsVerityDigestException if error + */ + public MerkleTree generateMerkleTree(InputStream inputStream, long size, + FsVerityHashAlgorithm fsVerityHashAlgorithm) throws FsVerityDigestException { + MerkleTree merkleTree; + try (MerkleTreeBuilder builder = new MerkleTreeBuilder()) { + merkleTree = builder.generateMerkleTree(inputStream, size, fsVerityHashAlgorithm); + } catch (IOException e) { + throw new FsVerityDigestException("IOException: " + e.getMessage()); + } catch (NoSuchAlgorithmException e) { + throw new FsVerityDigestException("Invalid algorithm:" + e.getMessage()); + } + return merkleTree; + } + + /** + * generate FsVerity digest of given input + * + * @param inputStream input stream for generate FsVerity digest + * @param size total size of input stream + * @param fsvTreeOffset merkle tree raw bytes offset based on the start of file + * @throws FsVerityDigestException if error + */ + public void generateFsVerityDigest(InputStream inputStream, long size, long fsvTreeOffset) + throws FsVerityDigestException { + MerkleTree merkleTree; + if (size == 0) { + merkleTree = new MerkleTree(null, null, FS_VERITY_HASH_ALGORITHM); + } else { + merkleTree = generateMerkleTree(inputStream, size, FS_VERITY_HASH_ALGORITHM); + } + int flags = fsvTreeOffset == 0 ? 0 : FsVerityDescriptor.FLAG_STORE_MERKLE_TREE_OFFSET; + byte[] fsVerityDescriptor = FsVerityDescriptor.getDescriptor(size, FS_VERITY_HASH_ALGORITHM.getId(), + LOG_2_OF_FSVERITY_HASH_PAGE_SIZE, salt, merkleTree.rootHash, flags, fsvTreeOffset); + 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 00000000..7d188413 --- /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 00000000..eebfdd28 --- /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 00000000..cbc198e9 --- /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 00000000..2d7591a8 --- /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 00000000..dd9e2914 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/sign/CentralDirectory.java @@ -0,0 +1,119 @@ +/* + * 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(char compressionMethod, char fileNameLength, char extraFieldLength, char fileCommentLength, + int relativeOffsetOfLocalHeader, byte[] fileName) { + this.compressionMethod = compressionMethod; + this.fileNameLength = fileNameLength; + this.extraFieldLength = extraFieldLength; + this.fileCommentLength = fileCommentLength; + this.relativeOffsetOfLocalHeader = relativeOffsetOfLocalHeader; + this.fileName = 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); + } +} 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 00000000..6af0aae6 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/sign/CodeSigning.java @@ -0,0 +1,409 @@ +/* + * Copyright (c) 2023-2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ohos.hapsigntool.codesigning.sign; + +import com.ohos.hapsigntool.codesigning.datastructure.CodeSignBlock; +import com.ohos.hapsigntool.codesigning.datastructure.ElfSignBlock; +import com.ohos.hapsigntool.codesigning.datastructure.Extension; +import com.ohos.hapsigntool.codesigning.datastructure.FsVerityInfoSegment; +import com.ohos.hapsigntool.codesigning.datastructure.MerkleTreeExtension; +import com.ohos.hapsigntool.codesigning.datastructure.SignInfo; +import com.ohos.hapsigntool.codesigning.exception.CodeSignException; +import com.ohos.hapsigntool.codesigning.exception.FsVerityDigestException; +import com.ohos.hapsigntool.codesigning.fsverity.FsVerityDescriptor; +import com.ohos.hapsigntool.codesigning.fsverity.FsVerityGenerator; +import com.ohos.hapsigntool.codesigning.utils.HapUtils; +import com.ohos.hapsigntool.hap.config.SignerConfig; +import com.ohos.hapsigntool.hap.entity.Pair; +import com.ohos.hapsigntool.hap.exception.HapFormatException; +import com.ohos.hapsigntool.signer.LocalSigner; +import com.ohos.hapsigntool.zip.RandomAccessFileZipDataInput; +import com.ohos.hapsigntool.zip.ZipDataInput; +import com.ohos.hapsigntool.zip.ZipFileInfo; +import com.ohos.hapsigntool.zip.ZipUtils; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; +import java.util.Locale; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +/** + * core functions of code signing + * + * @since 2023/06/05 + */ +public class CodeSigning { + /** + * Only hap and hsp bundle supports code signing, concatenate then with '/' for verification usage + */ + public static final String SUPPORT_FILE_FORM = "hap/hsp"; + + /** + * Only elf file supports bin code signing, concatenate then with '/' for verification usage + */ + public static final String SUPPORT_BIN_FILE_FORM = "elf"; + + /** + * Defined entry name of hap file + */ + public static final String HAP_SIGNATURE_ENTRY_NAME = "Hap"; + + private static final Logger LOGGER = LogManager.getLogger(CodeSigning.class); + + private static final String NATIVE_LIB_AN_SUFFIX = ".an"; + + private static final String NATIVE_LIB_SO_SUFFIX = ".so"; + + private final List extractedNativeLibSuffixs = new ArrayList<>(); + + private final SignerConfig signConfig; + + private CodeSignBlock codeSignBlock; + + private long timestamp = 0L; + + /** + * provide code sign functions to sign a hap + * + * @param signConfig configuration of sign + */ + public CodeSigning(SignerConfig signConfig) { + this.signConfig = signConfig; + } + + /** + * Sign the given elf file, and pack all signature into output file + * + * @param input file to sign + * @param offset position of codesign block based on start of the file + * @param inForm file's format + * @return byte array of code sign block + * @throws CodeSignException code signing exception + * @throws IOException io error + * @throws FsVerityDigestException computing FsVerity digest error + */ + public byte[] getElfCodeSignBlock(File input, long offset, String inForm) + throws CodeSignException, FsVerityDigestException, IOException { + if (!SUPPORT_BIN_FILE_FORM.contains(inForm)) { + throw new CodeSignException("file's format is unsupported"); + } + ElfSignBlock signBlock = new ElfSignBlock.ElfSignBlockBuilder().build(); + long fileSize = input.length(); + long fsvTreeOffset = signBlock.computeMerkleTreeOffset(offset); + // add fs-verify info + signBlock.addFsVerityInfo(FsVerityDescriptor.VERSION, FsVerityGenerator.getFsVerityHashAlgorithm(), + FsVerityGenerator.getLog2BlockSize()); + try (FileInputStream inputStream = new FileInputStream(input)) { + FsVerityGenerator fsVerityGenerator = new FsVerityGenerator(); + fsVerityGenerator.generateFsVerityDigest(inputStream, fileSize, fsvTreeOffset); + byte[] fsVerityDigest = fsVerityGenerator.getFsVerityDigest(); + byte[] signature = generateSignature(fsVerityDigest); + // add sign info + signBlock.addSignInfo(fsVerityGenerator.getSaltSize(), FsVerityDescriptor.FLAG_STORE_MERKLE_TREE_OFFSET, + fileSize, fsVerityGenerator.getSalt(), signature); + // add merkle tree info + signBlock.addMerkleTreeInfo(fsVerityGenerator.getTreeBytes(), fsvTreeOffset, + fsVerityGenerator.getRootHash()); + LOGGER.info("Sign successfully."); + return signBlock.toByteArray(); + } + } + + /** + * Sign the given hap file, and pack all signature into output file + * + * @param input file to sign + * @param offset position of codesign block based on start of the file + * @param inForm file's format + * @return byte array of code sign block + * @throws CodeSignException code signing exception + * @throws IOException io error + * @throws HapFormatException hap format invalid + * @throws FsVerityDigestException computing FsVerity digest error + */ + public byte[] getCodeSignBlock(File input, long offset, String inForm) + throws CodeSignException, IOException, HapFormatException, FsVerityDigestException { + LOGGER.info("Start to sign code."); + if (SUPPORT_BIN_FILE_FORM.contains(inForm)) { + return getElfCodeSignBlock(input, offset, inForm); + } + if (!SUPPORT_FILE_FORM.contains(inForm)) { + throw new CodeSignException("file's format is unsupported"); + } + long dataSize = computeDataSize(input); + timestamp = System.currentTimeMillis(); + // generate CodeSignBlock + this.codeSignBlock = new CodeSignBlock(); + // compute merkle tree offset, replace with computeMerkleTreeOffset if fs-verity descriptor supports + long fsvTreeOffset = this.codeSignBlock.computeMerkleTreeOffset(offset); + // update fs-verity segment + FsVerityInfoSegment fsVerityInfoSegment = new FsVerityInfoSegment(FsVerityDescriptor.VERSION, + FsVerityGenerator.getFsVerityHashAlgorithm(), FsVerityGenerator.getLog2BlockSize()); + this.codeSignBlock.setFsVerityInfoSegment(fsVerityInfoSegment); + + LOGGER.debug("Sign hap."); + FileInputStream inputStream = new FileInputStream(input); + Pair hapSignInfoAndMerkleTreeBytesPair = signFile(inputStream, dataSize, true, fsvTreeOffset); + // update hap segment in CodeSignBlock + this.codeSignBlock.getHapInfoSegment().setSignInfo(hapSignInfoAndMerkleTreeBytesPair.getFirst()); + // Insert merkle tree bytes into code sign block + this.codeSignBlock.addOneMerkleTree(HAP_SIGNATURE_ENTRY_NAME, hapSignInfoAndMerkleTreeBytesPair.getSecond()); + + // update native lib info segment in CodeSignBlock + signNativeLibs(input); + + // last update codeSignBlock before generating its byte array representation + updateCodeSignBlock(this.codeSignBlock); + + // complete code sign block byte array here + byte[] generated = this.codeSignBlock.generateCodeSignBlockByte(fsvTreeOffset); + LOGGER.info("Sign successfully."); + return generated; + } + + private long computeDataSize(File file) throws IOException, HapFormatException { + // parse central directory + RandomAccessFile outputHap = new RandomAccessFile(file, "rw"); + ZipDataInput outputHapIn = new RandomAccessFileZipDataInput(outputHap); + ZipFileInfo zipInfo = ZipUtils.findZipInfo(outputHapIn); + long centralDirectoryOffset = zipInfo.getCentralDirectoryOffset(); + int centralDirectorySize = zipInfo.getCentralDirectorySize(); + int centralDirectoryEntryCount = zipInfo.getCentralDirectoryEntryCount(); + // centralDirectoryOffset is where all data ends, including abc/so/an and resources + FileInputStream input = new FileInputStream(file); + input.skip(centralDirectoryOffset); + byte[] centralDirectoryBuffer = new byte[centralDirectorySize]; + input.read(centralDirectoryBuffer); + List cdList = parseCentralDirectory(centralDirectoryBuffer, centralDirectoryEntryCount); + long dataSize = 0L; + for (CentralDirectory entry : cdList) { + if (!(entry.isCodeFile() && entry.isUncompressed())) { + // if the first file is not uncompressed abc or so, set dataSize to zero + if (entry.getRelativeOffsetOfLocalHeader() == 0) { + dataSize = 0; + break; + } + // the first entry which is not abc/so/an is found, return its data offset + dataSize = entry.getRelativeOffsetOfLocalHeader() + JarFile.LOCHDR + entry.getFileNameLength() + + entry.getExtraFieldLength(); + break; + } + } + if ((dataSize % CodeSignBlock.PAGE_SIZE_4K) != 0) { + throw new HapFormatException( + String.format(Locale.ROOT, "Invalid dataSize(%d), not a multiple of 4096", dataSize)); + } + return dataSize; + } + + private void signNativeLibs(File input) throws IOException, FsVerityDigestException, CodeSignException { + // 'an' libs are always signed + extractedNativeLibSuffixs.add(NATIVE_LIB_AN_SUFFIX); + if (HapUtils.checkCompressNativeLibs(input)) { + LOGGER.info("compressNativeLibs equals true, sign so libs as well."); + // sign so libs only if compressNativeLibs equals true + extractedNativeLibSuffixs.add(NATIVE_LIB_SO_SUFFIX); + } + + // sign native files + JarFile inputJar = new JarFile(input, false); + List entryNames = getNativeEntriesFromHap(inputJar); + if (entryNames.isEmpty()) { + LOGGER.info("No native libs."); + return; + } + List> nativeLibInfoList = signFilesFromJar(entryNames, inputJar); + // update SoInfoSegment in CodeSignBlock + this.codeSignBlock.getSoInfoSegment().setSoInfoList(nativeLibInfoList); + } + + /** + * Get entry name of all native files in hap + * + * @param hap the given hap + * @return list of entry name + */ + private List getNativeEntriesFromHap(JarFile hap) { + List result = new ArrayList<>(); + for (Enumeration e = hap.entries(); e.hasMoreElements();) { + JarEntry entry = e.nextElement(); + if (!entry.isDirectory()) { + if (!isNativeFile(entry.getName())) { + continue; + } + result.add(entry.getName()); + } + } + return result; + } + + /** + * Check whether the entry is a native file + * + * @param entryName the name of entry + * @return true if it is a native file, and false otherwise + */ + private boolean isNativeFile(String entryName) { + for (String suffix : extractedNativeLibSuffixs) { + if (entryName.endsWith(suffix)) { + return true; + } + } + return false; + } + + /** + * Sign specific entries in a hap + * + * @param entryNames list of entries which need to be signed + * @param hap input hap + * @return sign info and merkle tree of each file + * @throws IOException io error + * @throws FsVerityDigestException computing FsVerity digest error + * @throws CodeSignException sign error + */ + private List> signFilesFromJar(List entryNames, JarFile hap) + throws IOException, FsVerityDigestException, CodeSignException { + List> nativeLibInfoList = new ArrayList<>(); + for (String name : entryNames) { + LOGGER.debug("Sign entry name = " + name); + JarEntry inEntry = hap.getJarEntry(name); + try (InputStream inputStream = hap.getInputStream(inEntry)) { + long fileSize = inEntry.getSize(); + // We don't store merkle tree in code signing of native libs + // Therefore, the second value of pair returned is ignored + Pair pairSignInfoAndMerkleTreeBytes = signFile(inputStream, fileSize, false, 0); + nativeLibInfoList.add(Pair.create(name, pairSignInfoAndMerkleTreeBytes.getFirst())); + } + } + return nativeLibInfoList; + } + + /** + * Sign a file from input stream + * + * @param inputStream input stream of a file + * @param fileSize size of the file + * @param storeTree whether to store merkle tree in signed info + * @param fsvTreeOffset merkle tree raw bytes offset based on the start of file + * @return pair of signature and tree + * @throws FsVerityDigestException computing FsVerity Digest error + * @throws CodeSignException signing error + */ + public Pair signFile(InputStream inputStream, long fileSize, boolean storeTree, + long fsvTreeOffset) throws FsVerityDigestException, CodeSignException { + FsVerityGenerator fsVerityGenerator = new FsVerityGenerator(); + fsVerityGenerator.generateFsVerityDigest(inputStream, fileSize, fsvTreeOffset); + byte[] fsVerityDigest = fsVerityGenerator.getFsVerityDigest(); + byte[] signature = generateSignature(fsVerityDigest); + int flags = 0; + if (storeTree) { + flags = SignInfo.FLAG_MERKLE_TREE_INCLUDED; + } + SignInfo signInfo = new SignInfo(fsVerityGenerator.getSaltSize(), flags, fileSize, fsVerityGenerator.getSalt(), + signature); + // if store merkle tree in sign info + if (storeTree) { + int merkleTreeSize = fsVerityGenerator.getTreeBytes() == null ? 0 : fsVerityGenerator.getTreeBytes().length; + Extension merkleTreeExtension = new MerkleTreeExtension(merkleTreeSize, fsvTreeOffset, + fsVerityGenerator.getRootHash()); + signInfo.addExtension(merkleTreeExtension); + } + return Pair.create(signInfo, fsVerityGenerator.getTreeBytes()); + } + + private byte[] generateSignature(byte[] signedData) throws CodeSignException { + // signConfig is created by SignerFactory + if (!(signConfig.getSigner() instanceof LocalSigner)) { + if (signConfig.getCertificates().isEmpty()) { + throw new CodeSignException("No certificates configured for sign"); + } + } + return SignedDataGenerator.BC.generateSignedData(signedData, signConfig); + } + + /** + * At here, segment header, fsverity info/hap/so info segment, merkle tree + * segment should all be generated. + * code sign block size, segment number, offset is not updated. + * Try to update whatever could be updated here. + * + * @param codeSignBlock CodeSignBlock + */ + private void updateCodeSignBlock(CodeSignBlock codeSignBlock) { + // construct segment header list + codeSignBlock.setSegmentHeaders(); + // Compute and set segment number + codeSignBlock.setSegmentNum(); + // update code sign block header flag + codeSignBlock.setCodeSignBlockFlag(); + // compute segment offset + codeSignBlock.computeSegmentOffset(); + } + + private List parseCentralDirectory(byte[] buffer, int count) { + List cdList = new ArrayList<>(); + ByteBuffer cdBuffer = ByteBuffer.allocate(buffer.length).order(ByteOrder.LITTLE_ENDIAN); + cdBuffer.put(buffer); + cdBuffer.rewind(); + for (int i = 0; i < count; i++) { + byte[] bytesBeforeCompressionMethod = new byte[CentralDirectory.BYTE_SIZE_BEFORE_COMPRESSION_METHOD]; + cdBuffer.get(bytesBeforeCompressionMethod); + char compressionMode = cdBuffer.getChar(); + 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(); + 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 = new CentralDirectory(compressionMode, fileNameLength, extraFieldLength, + fileCommentLength, locHdrOffset, fileNameBuffer); + cdList.add(cd); + } + + return cdList; + } + + private void printErrorLog(Exception exception) { + if (exception != null) { + LOGGER.error("Code signing error: {}", exception.getMessage(), exception); + } + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/sign/SignedDataGenerator.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/sign/SignedDataGenerator.java new file mode 100644 index 00000000..03d1b2bb --- /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 00000000..1e18fa6b --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/sign/VerifyCodeSignature.java @@ -0,0 +1,311 @@ +/* + * Copyright (c) 2023-2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ohos.hapsigntool.codesigning.sign; + +import com.ohos.hapsigntool.codesigning.datastructure.CodeSignBlock; +import com.ohos.hapsigntool.codesigning.datastructure.CodeSignBlockHeader; +import com.ohos.hapsigntool.codesigning.datastructure.ElfSignBlock; +import com.ohos.hapsigntool.codesigning.datastructure.Extension; +import com.ohos.hapsigntool.codesigning.datastructure.FsVerityInfoSegment; +import com.ohos.hapsigntool.codesigning.datastructure.HapInfoSegment; +import com.ohos.hapsigntool.codesigning.datastructure.MerkleTreeExtension; +import com.ohos.hapsigntool.codesigning.datastructure.NativeLibInfoSegment; +import com.ohos.hapsigntool.codesigning.datastructure.SegmentHeader; +import com.ohos.hapsigntool.codesigning.exception.FsVerityDigestException; +import com.ohos.hapsigntool.codesigning.exception.VerifyCodeSignException; +import com.ohos.hapsigntool.codesigning.fsverity.FsVerityGenerator; +import com.ohos.hapsigntool.codesigning.utils.CmsUtils; +import com.ohos.hapsigntool.hap.entity.Pair; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.bouncycastle.cms.CMSException; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Enumeration; +import java.util.List; +import java.util.Locale; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +/** + * Verify code signature given a file with code sign block + * + * @since 2023/09/08 + */ +public class VerifyCodeSignature { + private static final Logger LOGGER = LogManager.getLogger(VerifyCodeSignature.class); + + private static final String NATIVE_LIB_AN_SUFFIX = ".an"; + + private static final String NATIVE_LIB_SO_SUFFIX = ".so"; + + private static final List EXTRACTED_NATIVE_LIB_SUFFIXS = new ArrayList<>(); + + static { + EXTRACTED_NATIVE_LIB_SUFFIXS.add(NATIVE_LIB_AN_SUFFIX); + EXTRACTED_NATIVE_LIB_SUFFIXS.add(NATIVE_LIB_SO_SUFFIX); + } + + /** + * Verify a signed elf's signature + * + * @param file signed elf file + * @param offset start position of code sign block based on the start of the elf file + * @param length byte size of code sign block + * @param fileFormat elf or hqf or hsp, etc. + * @return true if signature verify succeed and false otherwise + * @throws IOException If an input or output exception occurred + * @throws VerifyCodeSignException parsing result invalid + * @throws FsVerityDigestException if fs-verity digest generation failed + * @throws CMSException if signature verify failed + */ + public static boolean verifyElf(File file, long offset, long length, String fileFormat) + throws IOException, VerifyCodeSignException, FsVerityDigestException, CMSException { + if (!CodeSigning.SUPPORT_BIN_FILE_FORM.contains(fileFormat)) { + LOGGER.info("Not elf file, skip code signing verify"); + return true; + } + // 1) parse sign block to ElfCodeSignBlock object + ElfSignBlock elfSignBlock; + try (FileInputStream signedElf = new FileInputStream(file)) { + byte[] codeSignBlockBytes = new byte[(int) length]; + signedElf.skip(offset); + signedElf.read(codeSignBlockBytes); + elfSignBlock = ElfSignBlock.fromByteArray(codeSignBlockBytes, offset); + } + // 2) verify file data + try (FileInputStream signedElf = new FileInputStream(file)) { + verifySingleFile(signedElf, elfSignBlock.getDataSize(), elfSignBlock.getSignature(), + elfSignBlock.getTreeOffset(), elfSignBlock.getMerkleTreeData()); + } + return true; + } + + /** + * Verify a signed hap's signature + * + * @param file signed hap file + * @param offset start position of code sign block based on the start of the hap file + * @param length byte size of code sign block + * @param fileFormat hap or hqf or hsp, etc. + * @return true if signature verify succeed and false otherwise + * @throws IOException If an input or output exception occurred + * @throws VerifyCodeSignException parsing result invalid + * @throws FsVerityDigestException if fs-verity digest generation failed + * @throws CMSException if signature verify failed + */ + public static boolean verifyHap(File file, long offset, long length, String fileFormat) + throws IOException, VerifyCodeSignException, FsVerityDigestException, CMSException { + if (!CodeSigning.SUPPORT_FILE_FORM.contains(fileFormat)) { + LOGGER.info("Not hap or hsp file, skip code signing verify"); + return true; + } + CodeSignBlock csb = generateCodeSignBlock(file, offset, length); + // 2) verify hap + try (FileInputStream hap = new FileInputStream(file)) { + long dataSize = csb.getHapInfoSegment().getSignInfo().getDataSize(); + byte[] signature = csb.getHapInfoSegment().getSignInfo().getSignature(); + Extension extension = csb.getHapInfoSegment() + .getSignInfo() + .getExtensionByType(MerkleTreeExtension.MERKLE_TREE_INLINED); + MerkleTreeExtension mte = new MerkleTreeExtension(0, 0, null); + if (extension instanceof MerkleTreeExtension) { + mte = (MerkleTreeExtension) extension; + } + // temporary: merkle tree offset set to zero, change to merkleTreeOffset + verifySingleFile(hap, dataSize, signature, mte.getMerkleTreeOffset(), + csb.getOneMerkleTreeByFileName(CodeSigning.HAP_SIGNATURE_ENTRY_NAME)); + } + // 3) verify native libs + try (JarFile inputJar = new JarFile(file, false)) { + for (int i = 0; i < csb.getSoInfoSegment().getSectionNum(); i++) { + String entryName = csb.getSoInfoSegment().getFileNameList().get(i); + byte[] entrySig = csb.getSoInfoSegment().getSignInfoList().get(i).getSignature(); + JarEntry entry = inputJar.getJarEntry(entryName); + if (entry.getSize() != csb.getSoInfoSegment().getSignInfoList().get(i).getDataSize()) { + throw new VerifyCodeSignException( + String.format(Locale.ROOT, "Invalid dataSize of native lib %s", entryName)); + } + InputStream entryInputStream = inputJar.getInputStream(entry); + // temporary merkleTreeOffset 0 + verifySingleFile(entryInputStream, entry.getSize(), entrySig, 0, null); + } + } + return true; + } + + private static CodeSignBlock generateCodeSignBlock(File file, long offset, long length) + throws IOException, VerifyCodeSignException { + CodeSignBlock csb = new CodeSignBlock(); + // 1) parse sign block to CodeSignBlock object + try (FileInputStream signedHap = new FileInputStream(file)) { + int fileReadOffset = 0; + // 0) skip data part, but fileReadOffset remains at start(0) + signedHap.skip(offset); + // 1) parse codeSignBlockHeader + byte[] codeSignBlockHeaderByteArray = new byte[CodeSignBlockHeader.size()]; + fileReadOffset += signedHap.read(codeSignBlockHeaderByteArray); + csb.setCodeSignBlockHeader(CodeSignBlockHeader.fromByteArray(codeSignBlockHeaderByteArray)); + if (csb.getCodeSignBlockHeader().getBlockSize() != length) { + throw new VerifyCodeSignException("Invalid code Sign block size of setCodeSignBlockHeader"); + } + // 2) parse segment headers + for (int i = 0; i < csb.getCodeSignBlockHeader().getSegmentNum(); i++) { + byte[] segmentHeaderByteArray = new byte[SegmentHeader.SEGMENT_HEADER_LENGTH]; + fileReadOffset += signedHap.read(segmentHeaderByteArray); + csb.addToSegmentList(SegmentHeader.fromByteArray(segmentHeaderByteArray)); + } + // compute merkle tree offset by alignment, based on file start + long computedTreeOffset = getAlignmentAddr(CodeSignBlock.PAGE_SIZE_4K, fileReadOffset + offset); + // skip zero padding before merkle tree, adds zero padding length to fileReadOffset + fileReadOffset += signedHap.skip(computedTreeOffset - offset - fileReadOffset); + parseMerkleTree(csb, fileReadOffset, signedHap, computedTreeOffset); + } + return csb; + } + + private static void parseMerkleTree(CodeSignBlock csb, int readOffset, FileInputStream signedHap, + long computedTreeOffset) throws VerifyCodeSignException, IOException { + // check segment offset and segment size + byte[] merkleTreeBytes = new byte[0]; + int fileReadOffset = readOffset; + for (SegmentHeader segmentHeader : csb.getSegmentHeaderList()) { + if (fileReadOffset > segmentHeader.getSegmentOffset()) { + throw new VerifyCodeSignException("Invaild offset of merkle tree and segment header"); + } + // get merkle tree bytes + if (fileReadOffset < segmentHeader.getSegmentOffset()) { + merkleTreeBytes = new byte[segmentHeader.getSegmentOffset() - fileReadOffset]; + fileReadOffset += signedHap.read(merkleTreeBytes); + } + byte[] sh = new byte[segmentHeader.getSegmentSize()]; + fileReadOffset += signedHap.read(sh); + if (segmentHeader.getType() == SegmentHeader.CSB_FSVERITY_INFO_SEG) { + // 3) parse fs-verity info segment + csb.setFsVerityInfoSegment(FsVerityInfoSegment.fromByteArray(sh)); + } else if (segmentHeader.getType() == SegmentHeader.CSB_HAP_META_SEG) { + // 4) parse hap info segment + csb.setHapInfoSegment(HapInfoSegment.fromByteArray(sh)); + } else if (segmentHeader.getType() == SegmentHeader.CSB_NATIVE_LIB_INFO_SEG) { + // 5) parse so info segment + csb.setSoInfoSegment(NativeLibInfoSegment.fromByteArray(sh)); + } + } + if (fileReadOffset != csb.getCodeSignBlockHeader().getBlockSize()) { + throw new VerifyCodeSignException("Invalid blockSize of getCodeSignBlockHeader"); + } + // parse merkle tree + Extension extension = csb.getHapInfoSegment() + .getSignInfo() + .getExtensionByType(MerkleTreeExtension.MERKLE_TREE_INLINED); + if (extension == null) { + throw new VerifyCodeSignException("Missing merkleTreeExtension in verifycation"); + } + if (extension instanceof MerkleTreeExtension) { + MerkleTreeExtension mte = (MerkleTreeExtension) extension; + if (computedTreeOffset != mte.getMerkleTreeOffset()) { + throw new VerifyCodeSignException("Invalid merkle tree offset"); + } + if (merkleTreeBytes.length != mte.getMerkleTreeSize()) { + throw new VerifyCodeSignException("Invalid merkle tree size"); + } + csb.addOneMerkleTree(CodeSigning.HAP_SIGNATURE_ENTRY_NAME, merkleTreeBytes); + } + } + + private static long getAlignmentAddr(long alignment, long input) { + long residual = input % alignment; + if (residual == 0) { + return input; + } else { + return input + (alignment - residual); + } + } + + /** + * Verifies the signature of a given file with its signature in pkcs#7 format + * + * @param input file being verified in InputStream representation + * @param length size of signed data in the file + * @param signature byte array of signature in pkcs#7 format + * @param merkleTreeOffset merkle tree offset based on file start + * @param inMerkleTreeBytes merkle tree raw bytes + * @throws FsVerityDigestException if fs-verity digest generation failed + * @throws CMSException if signature verify failed + * @throws VerifyCodeSignException parsing code sign block error + */ + public static void verifySingleFile(InputStream input, long length, byte[] signature, long merkleTreeOffset, + byte[] inMerkleTreeBytes) throws FsVerityDigestException, CMSException, VerifyCodeSignException { + Pair pairResult = generateFsVerityDigest(input, length, merkleTreeOffset); + byte[] generatedMerkleTreeBytes = pairResult.getSecond(); + if (generatedMerkleTreeBytes == null) { + generatedMerkleTreeBytes = new byte[0]; + } + // For native libs, inMerkleTreeBytes is null, skip check here + if ((inMerkleTreeBytes != null) && !Arrays.equals(inMerkleTreeBytes, generatedMerkleTreeBytes)) { + throw new VerifyCodeSignException("verify merkle tree bytes failed"); + } + CmsUtils.verifySignDataWithUnsignedDataDigest(pairResult.getFirst(), signature); + } + + private static Pair generateFsVerityDigest(InputStream inputStream, long size, + long merkleTreeOffset) throws FsVerityDigestException { + FsVerityGenerator fsVerityGenerator = new FsVerityGenerator(); + fsVerityGenerator.generateFsVerityDigest(inputStream, size, merkleTreeOffset); + return Pair.create(fsVerityGenerator.getFsVerityDigest(), fsVerityGenerator.getTreeBytes()); + } + + /** + * Get entry name of all native files in hap + * + * @param hap the given hap + * @return list of entry name + */ + private static List getNativeEntriesFromHap(JarFile hap) { + List result = new ArrayList<>(); + for (Enumeration e = hap.entries(); e.hasMoreElements();) { + JarEntry entry = e.nextElement(); + if (!entry.isDirectory()) { + if (!isNativeFile(entry.getName())) { + continue; + } + result.add(entry.getName()); + } + } + return result; + } + + /** + * Check whether the entry is a native file + * + * @param entryName the name of entry + * @return true if it is a native file, and false otherwise + */ + private static boolean isNativeFile(String entryName) { + for (String suffix : EXTRACTED_NATIVE_LIB_SUFFIXS) { + if (entryName.endsWith(suffix)) { + return true; + } + } + return false; + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/utils/CmsUtils.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/utils/CmsUtils.java new file mode 100644 index 00000000..df4b11a7 --- /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 00000000..aafbaf57 --- /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 00000000..d3c9ab4f --- /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 00000000..816ea38c --- /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; + } +} -- Gitee From 07866da15e668f86a27ca66ac998ec0019ea8526 Mon Sep 17 00:00:00 2001 From: zfeixiang Date: Mon, 30 Oct 2023 14:03:57 +0800 Subject: [PATCH 08/15] =?UTF-8?q?=E5=BA=94=E7=94=A8=E5=8F=8A=E4=BA=8C?= =?UTF-8?q?=E8=BF=9B=E5=88=B6=E6=96=87=E4=BB=B6=E4=BB=A3=E7=A0=81=E7=AD=BE?= =?UTF-8?q?=E5=90=8D=E8=83=BD=E5=8A=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: zfeixiang --- .../codesigning/datastructure/ElfSignBlock.java | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) 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 index 80158950..68d57842 100644 --- 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 @@ -56,11 +56,13 @@ public class ElfSignBlock { */ public static final int MERKLE_TREE_INLINED = 0x2; + public static final byte CODE_SIGN_VERSION = 0x1; + private static final int ROOT_HASH_SIZE = 64; private static final int SALT_BUFFER_LENGTH = 32; - private static final int RESERVED_BYTE_ARRAY_LENGTH = 128; + private static final int RESERVED_BYTE_ARRAY_LENGTH = 127; private static final int FSD_WITHOUT_SIGN_LENGTH = 256; @@ -101,6 +103,8 @@ public class ElfSignBlock { private byte[] reservedData; + private byte csVersion; + private byte[] signature; private ElfSignBlock(ElfSignBlockBuilder builder) { @@ -122,6 +126,7 @@ public class ElfSignBlock { this.reservedInt = builder.reservedInt; this.treeOffset = builder.treeOffset; this.reservedData = builder.reservedData; + this.csVersion = builder.csVersion; this.signature = builder.signature; } @@ -244,6 +249,7 @@ public class ElfSignBlock { bf.putInt(reservedInt); bf.putLong(treeOffset); bf.put(reservedData); + bf.put(csVersion); bf.put(signature); return bf.array(); } @@ -289,7 +295,6 @@ public class ElfSignBlock { private static void fillFsd(ElfSignBlockBuilder builder, ByteBuffer bf, int inFsdLength) throws VerifyCodeSignException { - byte inFsVersion = bf.get(); if (FsVerityDescriptor.VERSION != inFsVersion) { throw new VerifyCodeSignException("Invalid fs-verify descriptor version of ElfSignBlock"); @@ -364,6 +369,8 @@ public class ElfSignBlock { private byte[] reservedData = new byte[RESERVED_BYTE_ARRAY_LENGTH]; + private byte csVersion = CODE_SIGN_VERSION; + private byte[] signature = new byte[0]; public ElfSignBlockBuilder setTreeType(int treeType) { @@ -456,6 +463,11 @@ public class ElfSignBlock { return this; } + public ElfSignBlockBuilder setCsVersion(byte csVersion) { + this.csVersion = csVersion; + return this; + } + public ElfSignBlockBuilder setSignature(byte[] signature) { this.signature = signature; return this; -- Gitee From 26b4f296b797b6064a2ce0e8b4c4bdd3627c7eb1 Mon Sep 17 00:00:00 2001 From: zfeixiang Date: Mon, 30 Oct 2023 18:09:18 +0800 Subject: [PATCH 09/15] =?UTF-8?q?=E5=BA=94=E7=94=A8=E5=8F=8A=E4=BA=8C?= =?UTF-8?q?=E8=BF=9B=E5=88=B6=E6=96=87=E4=BB=B6=E4=BB=A3=E7=A0=81=E7=AD=BE?= =?UTF-8?q?=E5=90=8D=E8=83=BD=E5=8A=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: zfeixiang --- .../codesigning/datastructure/ElfSignBlock.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 index 68d57842..110f0335 100644 --- 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 @@ -56,6 +56,9 @@ public class ElfSignBlock { */ public static final int MERKLE_TREE_INLINED = 0x2; + /** + * code sign version + */ public static final byte CODE_SIGN_VERSION = 0x1; private static final int ROOT_HASH_SIZE = 64; @@ -303,9 +306,6 @@ public class ElfSignBlock { byte inLog2BlockSize = bf.get(); builder.setFsVersion(inFsVersion).setFsHashAlgorithm(inFsHashAlgorithm).setLog2BlockSize(inLog2BlockSize); byte inSaltSize = bf.get(); - if (SALT_BUFFER_LENGTH != inSaltSize) { - throw new VerifyCodeSignException("Invalid salt size of ElfSignBlock"); - } int inSignSize = bf.getInt(); if (inFsdLength != inSignSize + FSD_WITHOUT_SIGN_LENGTH) { throw new VerifyCodeSignException("Invalid sign size of ElfSignBlock"); @@ -314,7 +314,7 @@ public class ElfSignBlock { byte[] inRootHash = new byte[ROOT_HASH_SIZE]; bf.get(inRootHash); builder.setSaltSize(inSaltSize).setSignSize(inSignSize).setDataSize(inDataSize).setRootHash(inRootHash); - byte[] inSalt = new byte[inSaltSize]; + byte[] inSalt = new byte[SALT_BUFFER_LENGTH]; bf.get(inSalt); int inFlags = bf.getInt(); bf.getInt(); -- Gitee From fb5c38778e03950b8af1c33a08fd264961b9b095 Mon Sep 17 00:00:00 2001 From: zfeixiang Date: Tue, 31 Oct 2023 15:28:27 +0800 Subject: [PATCH 10/15] =?UTF-8?q?=E5=BA=94=E7=94=A8=E5=8F=8A=E4=BA=8C?= =?UTF-8?q?=E8=BF=9B=E5=88=B6=E6=96=87=E4=BB=B6=E4=BB=A3=E7=A0=81=E7=AD=BE?= =?UTF-8?q?=E5=90=8D=E8=83=BD=E5=8A=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: zfeixiang --- .../datastructure/CodeSignBlock.java | 4 +- .../datastructure/CodeSignBlockHeader.java | 87 ++++++++++---- .../datastructure/ElfSignBlock.java | 51 ++++----- .../datastructure/NativeLibInfoSegment.java | 106 +++++++++++++----- .../fsverity/FsVerityDescriptor.java | 96 ++++++++++++++-- .../fsverity/FsVerityGenerator.java | 6 +- .../codesigning/sign/CentralDirectory.java | 63 +++++++++-- .../codesigning/sign/CodeSigning.java | 8 +- 8 files changed, 317 insertions(+), 104 deletions(-) diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/CodeSignBlock.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/CodeSignBlock.java index d253bfe6..40679e46 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/CodeSignBlock.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/CodeSignBlock.java @@ -70,11 +70,11 @@ public class CodeSignBlock { private final Map merkleTreeMap; public CodeSignBlock() { - this.codeSignBlockHeader = new CodeSignBlockHeader(); + this.codeSignBlockHeader = new CodeSignBlockHeader.Builder().build(); this.segmentHeaderList = new ArrayList<>(); this.fsVerityInfoSegment = new FsVerityInfoSegment(); this.hapInfoSegment = new HapInfoSegment(); - this.nativeLibInfoSegment = new NativeLibInfoSegment(); + this.nativeLibInfoSegment = new NativeLibInfoSegment.Builder().build(); this.merkleTreeMap = new HashMap<>(); } 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 index 6848b2e4..1e81a60a 100644 --- 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 @@ -62,9 +62,9 @@ public class CodeSignBlockHeader { // 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 = MAGIC_NUM; + private long magic; - private int version = CODE_SIGNING_VERSION; + private int version; private int blockSize; @@ -72,31 +72,20 @@ public class CodeSignBlockHeader { private int flags; - private byte[] reserved = new byte[RESERVED_BYTE_ARRAY_LENGTH]; - - /** - * Default construct of CodeSignBlockHeader - */ - public CodeSignBlockHeader() { - } + private byte[] reserved; /** * Construct of CodeSignBlockHeader * - * @param magic magic number - * @param version version - * @param blockSize code sign block size - * @param segmentNum segment number - * @param flags flags - * @param reserved reserved + * @param builder builder */ - public CodeSignBlockHeader(long magic, int version, int blockSize, int segmentNum, int flags, byte[] reserved) { - this.magic = magic; - this.version = version; - this.blockSize = blockSize; - this.segmentNum = segmentNum; - this.flags = flags; - this.reserved = reserved; + 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) { @@ -169,7 +158,8 @@ public class CodeSignBlockHeader { } byte[] inReserved = new byte[RESERVED_BYTE_ARRAY_LENGTH]; bf.get(inReserved); - return new CodeSignBlockHeader(inMagic, inVersion, inBlockSize, inSegmentNum, inFlags, inReserved); + return new Builder().setMagic(inMagic).setVersion(inVersion).setBlockSize(inBlockSize) + .setSegmentNum(inSegmentNum).setFlags(inFlags).setReserved(inReserved).build(); } /** @@ -191,4 +181,55 @@ public class CodeSignBlockHeader { "CodeSignBlockHeader{magic: %d, version: %d, blockSize: %d, segmentNum: %d, flags: %d}", this.magic, this.version, this.blockSize, this.segmentNum, this.flags); } + + /** + * Builder of CodeSignBlockHeader class + */ + public static class Builder { + private long magic = MAGIC_NUM; + + private int version = CODE_SIGNING_VERSION; + + private int blockSize; + + private int segmentNum; + + private int flags; + + private byte[] reserved = new byte[RESERVED_BYTE_ARRAY_LENGTH]; + + public Builder setMagic(long magic) { + this.magic = magic; + return this; + } + + public Builder setVersion(int version) { + this.version = version; + return this; + } + + public Builder setBlockSize(int blockSize) { + this.blockSize = blockSize; + return this; + } + + public Builder setSegmentNum(int segmentNum) { + this.segmentNum = segmentNum; + return this; + } + + public Builder setFlags(int flags) { + this.flags = flags; + return this; + } + + public Builder setReserved(byte[] reserved) { + this.reserved = reserved; + return this; + } + + public CodeSignBlockHeader build() { + return new CodeSignBlockHeader(this); + } + } } diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/ElfSignBlock.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/datastructure/ElfSignBlock.java index 110f0335..c9a13268 100644 --- 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 @@ -41,7 +41,8 @@ import java.util.Arrays; * 15) u32 reserved * 16) u64 treeOffset: merkle tree offset * 17) u8[128] reserved - * 18) u8[] signature: signature after signing the data in byte array representation + * 18) u8 csVersion: code sign version + * 19) u8[] signature: signature after signing the data in byte array representation * * @since 2023/09/08 */ @@ -110,7 +111,7 @@ public class ElfSignBlock { private byte[] signature; - private ElfSignBlock(ElfSignBlockBuilder builder) { + private ElfSignBlock(Builder builder) { this.treeType = builder.treeType; this.treeLength = builder.treeLength; this.paddingBeforeTree = builder.paddingBeforeTree; @@ -270,7 +271,7 @@ public class ElfSignBlock { bf.put(bytes); // after put, rewind is mandatory before get bf.rewind(); - ElfSignBlockBuilder builder = new ElfSignBlockBuilder(); + Builder builder = new Builder(); int inTreeType = bf.getInt(); if (MERKLE_TREE_INLINED != inTreeType) { throw new VerifyCodeSignException("Invalid merkle tree type of ElfSignBlock"); @@ -296,7 +297,7 @@ public class ElfSignBlock { return builder.build(); } - private static void fillFsd(ElfSignBlockBuilder builder, ByteBuffer bf, int inFsdLength) + private static void fillFsd(Builder builder, ByteBuffer bf, int inFsdLength) throws VerifyCodeSignException { byte inFsVersion = bf.get(); if (FsVerityDescriptor.VERSION != inFsVersion) { @@ -331,7 +332,7 @@ public class ElfSignBlock { /** * Builder of ElfSignBlock class */ - public static class ElfSignBlockBuilder { + public static class Builder { private int treeType = MERKLE_TREE_INLINED; private int treeLength; @@ -373,102 +374,102 @@ public class ElfSignBlock { private byte[] signature = new byte[0]; - public ElfSignBlockBuilder setTreeType(int treeType) { + public Builder setTreeType(int treeType) { this.treeType = treeType; return this; } - public ElfSignBlockBuilder setTreeLength(int treeLength) { + public Builder setTreeLength(int treeLength) { this.treeLength = treeLength; return this; } - public ElfSignBlockBuilder setPaddingBeforeTree(byte[] paddingBeforeTree) { + public Builder setPaddingBeforeTree(byte[] paddingBeforeTree) { this.paddingBeforeTree = paddingBeforeTree; return this; } - public ElfSignBlockBuilder setMerkleTreeData(byte[] merkleTreeData) { + public Builder setMerkleTreeData(byte[] merkleTreeData) { this.merkleTreeData = merkleTreeData; return this; } - public ElfSignBlockBuilder setFsdType(int fsdType) { + public Builder setFsdType(int fsdType) { this.fsdType = fsdType; return this; } - public ElfSignBlockBuilder setFsdLength(int fsdLength) { + public Builder setFsdLength(int fsdLength) { this.fsdLength = fsdLength; return this; } - public ElfSignBlockBuilder setFsVersion(byte fsVersion) { + public Builder setFsVersion(byte fsVersion) { this.fsVersion = fsVersion; return this; } - public ElfSignBlockBuilder setFsHashAlgorithm(byte fsHashAlgorithm) { + public Builder setFsHashAlgorithm(byte fsHashAlgorithm) { this.fsHashAlgorithm = fsHashAlgorithm; return this; } - public ElfSignBlockBuilder setLog2BlockSize(byte log2BlockSize) { + public Builder setLog2BlockSize(byte log2BlockSize) { this.log2BlockSize = log2BlockSize; return this; } - public ElfSignBlockBuilder setSaltSize(byte saltSize) { + public Builder setSaltSize(byte saltSize) { this.saltSize = saltSize; return this; } - public ElfSignBlockBuilder setSignSize(int signSize) { + public Builder setSignSize(int signSize) { this.signSize = signSize; return this; } - public ElfSignBlockBuilder setDataSize(long dataSize) { + public Builder setDataSize(long dataSize) { this.dataSize = dataSize; return this; } - public ElfSignBlockBuilder setRootHash(byte[] rootHash) { + public Builder setRootHash(byte[] rootHash) { this.rootHash = rootHash; return this; } - public ElfSignBlockBuilder setSalt(byte[] salt) { + public Builder setSalt(byte[] salt) { this.salt = salt; return this; } - public ElfSignBlockBuilder setFlags(int flags) { + public Builder setFlags(int flags) { this.flags = flags; return this; } - public ElfSignBlockBuilder setReservedInt(int reservedInt) { + public Builder setReservedInt(int reservedInt) { this.reservedInt = reservedInt; return this; } - public ElfSignBlockBuilder setTreeOffset(long treeOffset) { + public Builder setTreeOffset(long treeOffset) { this.treeOffset = treeOffset; return this; } - public ElfSignBlockBuilder setReservedData(byte[] reservedData) { + public Builder setReservedData(byte[] reservedData) { this.reservedData = reservedData; return this; } - public ElfSignBlockBuilder setCsVersion(byte csVersion) { + public Builder setCsVersion(byte csVersion) { this.csVersion = csVersion; return this; } - public ElfSignBlockBuilder setSignature(byte[] signature) { + public Builder setSignature(byte[] signature) { this.signature = signature; return this; } 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 index 98dd744d..c1feeb7f 100644 --- 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 @@ -64,7 +64,7 @@ public class NativeLibInfoSegment { private static final int ALIGNMENT_FOR_SIGNINFO = 4; - private int magic = MAGIC_NUM; + private int magic; private int segmentSize; @@ -72,44 +72,31 @@ public class NativeLibInfoSegment { private List> soInfoList = new ArrayList<>(); - private List signedFilePosList = new ArrayList<>(); + private List signedFilePosList; - private List fileNameList = new ArrayList<>(); + private List fileNameList; - private List signInfoList = new ArrayList<>(); + private List signInfoList; - private byte[] zeroPadding = new byte[0]; + private byte[] zeroPadding; private int fileNameListBlockSize; private int signInfoListBlockSize; /** - * Default constructor - */ - public NativeLibInfoSegment() { - } - - /** - * Constructor for SoInfoSegment by byte array + * Constructor for SoInfoSegment * - * @param magic byte array representation of SoInfoSegment object - * @param segmentSize byte size of NativeLibInfoSegment - * @param sectionNum number of native libs - * @param signedFilePosList offset and size of each file - * @param fileNameList file name of each signed file - * @param signInfoList sign info of each signed file - * @param zeroPadding zero padding before sign info list + * @param builder Builder */ - public NativeLibInfoSegment(int magic, int segmentSize, int sectionNum, List signedFilePosList, - List fileNameList, List signInfoList, byte[] zeroPadding) { - this.magic = magic; - this.segmentSize = segmentSize; - this.sectionNum = sectionNum; - this.signedFilePosList = signedFilePosList; - this.fileNameList = fileNameList; - this.signInfoList = signInfoList; - this.zeroPadding = zeroPadding; + 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; } /** @@ -264,8 +251,9 @@ public class NativeLibInfoSegment { bf.get(signInfoBuffer); inSignInfoList.add(SignInfo.fromByteArray(signInfoBuffer)); } - return new NativeLibInfoSegment(inMagic, inSegmentSize, inSectionNum, inSignedFilePosList, inFileNameList, - inSignInfoList, inZeroPadding); + return new Builder().setMagic(inMagic).setSegmentSize(inSegmentSize).setSectionNum(inSectionNum) + .setSignedFilePosList(inSignedFilePosList).setFileNameList(inFileNameList) + .setSignInfoList(inSignInfoList).setZeroPadding(inZeroPadding).build(); } /** @@ -280,4 +268,62 @@ public class NativeLibInfoSegment { Arrays.toString(this.signedFilePosList.toArray()), Arrays.toString(this.fileNameList.toArray()), Arrays.toString(this.zeroPadding), Arrays.toString(this.signInfoList.toArray())); } + + /** + * Builder of NativeLibInfoSegment class + */ + public static class Builder { + private int magic = MAGIC_NUM; + + private int segmentSize; + + private int sectionNum; + + private List signedFilePosList = new ArrayList<>(); + + private List fileNameList = new ArrayList<>(); + + private List signInfoList = new ArrayList<>(); + + private byte[] zeroPadding = new byte[0]; + + public Builder setMagic(int magic) { + this.magic = magic; + return this; + } + + public Builder setSegmentSize(int segmentSize) { + this.segmentSize = segmentSize; + return this; + } + + public Builder setSectionNum(int sectionNum) { + this.sectionNum = sectionNum; + return this; + } + + public Builder setSignedFilePosList(List signedFilePosList) { + this.signedFilePosList = signedFilePosList; + return this; + } + + public Builder setFileNameList(List fileNameList) { + this.fileNameList = fileNameList; + return this; + } + + public Builder setSignInfoList(List signInfoList) { + this.signInfoList = signInfoList; + return this; + } + + public Builder setZeroPadding(byte[] zeroPadding) { + this.zeroPadding = zeroPadding; + return this; + } + + public NativeLibInfoSegment build() { + return new NativeLibInfoSegment(this); + } + } } diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/fsverity/FsVerityDescriptor.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/fsverity/FsVerityDescriptor.java index d29de6f6..19c01730 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 @@ -63,21 +63,37 @@ public class FsVerityDescriptor { private static final int RESERVED_SIZE_AFTER_FLAGS = 4; + private long fileSize; + + private byte hashAlgorithm; + + private byte log2BlockSize; + + private byte[] salt; + + private byte[] rawRootHash; + + private int flags; + + private long merkleTreeOffset; + + private FsVerityDescriptor(Builder builder) { + this.fileSize = builder.fileSize; + this.hashAlgorithm = builder.hashAlgorithm; + this.log2BlockSize = builder.log2BlockSize; + this.salt = builder.salt; + this.rawRootHash = builder.rawRootHash; + this.flags = builder.flags; + this.merkleTreeOffset = builder.merkleTreeOffset; + } + /** - * Get FsVerity descriptor + * Get FsVerity descriptor bytes * - * @param fileSize size of input - * @param hashAlgorithm hash algorithm id - * @param log2BlockSize log2 of hash block size - * @param salt salt used for hash - * @param rawRootHash root hash of merkle tree - * @param flags flag indicating whether merkle tree offset is present in fs-verity descriptor - * @param merkleTreeOffset merkle tree offset based on file start * @return bytes of descriptor * @throws FsVerityDigestException if error */ - public static byte[] getDescriptor(long fileSize, byte hashAlgorithm, byte log2BlockSize, byte[] salt, - byte[] rawRootHash, int flags, long merkleTreeOffset) throws FsVerityDigestException { + public byte[] toByteArray() throws FsVerityDigestException { ByteBuffer buffer = ByteBuffer.allocate(DESCRIPTOR_SIZE).order(ByteOrder.LITTLE_ENDIAN); buffer.put(VERSION); buffer.put(hashAlgorithm); @@ -106,7 +122,7 @@ public class FsVerityDescriptor { * @param src bytes to write * @param size size of written bytes, fill 0 if src bytes is long enough */ - private static void writeBytesWithSize(ByteBuffer buffer, byte[] src, int size) { + private void writeBytesWithSize(ByteBuffer buffer, byte[] src, int size) { int pos = buffer.position(); if (src != null) { if (src.length > size) { @@ -117,4 +133,62 @@ public class FsVerityDescriptor { } buffer.position(pos + size); } + + /** + * Builder of FsVerityDescriptor class + */ + public static class Builder { + private long fileSize; + + private byte hashAlgorithm; + + private byte log2BlockSize; + + private byte[] salt; + + private byte[] rawRootHash; + + private int flags; + + private long merkleTreeOffset; + + public Builder setFileSize(long fileSize) { + this.fileSize = fileSize; + return this; + } + + public Builder setHashAlgorithm(byte hashAlgorithm) { + this.hashAlgorithm = hashAlgorithm; + return this; + } + + public Builder setLog2BlockSize(byte log2BlockSize) { + this.log2BlockSize = log2BlockSize; + return this; + } + + public Builder setSalt(byte[] salt) { + this.salt = salt; + return this; + } + + public Builder setRawRootHash(byte[] rawRootHash) { + this.rawRootHash = rawRootHash; + return this; + } + + public Builder setFlags(int flags) { + this.flags = flags; + return this; + } + + public Builder setMerkleTreeOffset(long merkleTreeOffset) { + this.merkleTreeOffset = merkleTreeOffset; + return this; + } + + public FsVerityDescriptor build() { + return new FsVerityDescriptor(this); + } + } } diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/fsverity/FsVerityGenerator.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/fsverity/FsVerityGenerator.java index b46620e6..8e0ba460 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 @@ -85,8 +85,10 @@ public class FsVerityGenerator { merkleTree = generateMerkleTree(inputStream, size, FS_VERITY_HASH_ALGORITHM); } int flags = fsvTreeOffset == 0 ? 0 : FsVerityDescriptor.FLAG_STORE_MERKLE_TREE_OFFSET; - byte[] fsVerityDescriptor = FsVerityDescriptor.getDescriptor(size, FS_VERITY_HASH_ALGORITHM.getId(), - LOG_2_OF_FSVERITY_HASH_PAGE_SIZE, salt, merkleTree.rootHash, flags, fsvTreeOffset); + byte[] fsVerityDescriptor = new FsVerityDescriptor.Builder().setFileSize(size) + .setHashAlgorithm(FS_VERITY_HASH_ALGORITHM.getId()).setLog2BlockSize(LOG_2_OF_FSVERITY_HASH_PAGE_SIZE) + .setSalt(salt).setRawRootHash(merkleTree.rootHash).setFlags(flags).setMerkleTreeOffset(fsvTreeOffset) + .build().toByteArray(); byte[] digest; try { digest = DigestUtils.computeDigest(fsVerityDescriptor, FS_VERITY_HASH_ALGORITHM.getHashAlgorithm()); 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 index dd9e2914..bba3f206 100644 --- 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 @@ -55,14 +55,13 @@ public class CentralDirectory { private final byte[] fileName; - public CentralDirectory(char compressionMethod, char fileNameLength, char extraFieldLength, char fileCommentLength, - int relativeOffsetOfLocalHeader, byte[] fileName) { - this.compressionMethod = compressionMethod; - this.fileNameLength = fileNameLength; - this.extraFieldLength = extraFieldLength; - this.fileCommentLength = fileCommentLength; - this.relativeOffsetOfLocalHeader = relativeOffsetOfLocalHeader; - this.fileName = 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; } /** @@ -116,4 +115,52 @@ public class CentralDirectory { this.getFileName(), this.relativeOffsetOfLocalHeader, (int) this.fileNameLength, (int) this.extraFieldLength, (int) this.fileCommentLength); } + + public static class Builder { + private char compressionMethod; + + private char fileNameLength; + + private char extraFieldLength; + + private char fileCommentLength; + + private int relativeOffsetOfLocalHeader; + + private byte[] fileName; + + public Builder setCompressionMethod(char compressionMethod) { + this.compressionMethod = compressionMethod; + return this; + } + + public Builder setFileNameLength(char fileNameLength) { + this.fileNameLength = fileNameLength; + return this; + } + + public Builder setExtraFieldLength(char extraFieldLength) { + this.extraFieldLength = extraFieldLength; + return this; + } + + public Builder setFileCommentLength(char fileCommentLength) { + this.fileCommentLength = fileCommentLength; + return this; + } + + public Builder setRelativeOffsetOfLocalHeader(int relativeOffsetOfLocalHeader) { + this.relativeOffsetOfLocalHeader = relativeOffsetOfLocalHeader; + return this; + } + + public Builder setFileName(byte[] fileName) { + this.fileName = fileName; + return this; + } + + public CentralDirectory build() { + return new CentralDirectory(this); + } + } } diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/sign/CodeSigning.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/sign/CodeSigning.java index 6af0aae6..7804c4b2 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 @@ -112,7 +112,7 @@ public class CodeSigning { if (!SUPPORT_BIN_FILE_FORM.contains(inForm)) { throw new CodeSignException("file's format is unsupported"); } - ElfSignBlock signBlock = new ElfSignBlock.ElfSignBlockBuilder().build(); + ElfSignBlock signBlock = new ElfSignBlock.Builder().build(); long fileSize = input.length(); long fsvTreeOffset = signBlock.computeMerkleTreeOffset(offset); // add fs-verify info @@ -375,6 +375,7 @@ public class CodeSigning { 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); @@ -385,6 +386,8 @@ public class CodeSigning { = 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) { @@ -393,8 +396,7 @@ public class CodeSigning { if (fileCommentLength != 0) { cdBuffer.get(new byte[fileCommentLength]); } - CentralDirectory cd = new CentralDirectory(compressionMode, fileNameLength, extraFieldLength, - fileCommentLength, locHdrOffset, fileNameBuffer); + CentralDirectory cd = builder.setFileName(fileNameBuffer).build(); cdList.add(cd); } -- Gitee From 902de7356c980e113e3bafdd39073caf81b7a7b7 Mon Sep 17 00:00:00 2001 From: yangmingming Date: Wed, 1 Nov 2023 10:14:27 +0800 Subject: [PATCH 11/15] =?UTF-8?q?=E7=89=88=E6=9C=AC=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E5=B9=B4=E4=BB=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: yangmingming --- .../main/java/com/ohos/hapsigntool/api/model/Options.java | 5 ----- .../ohos/hapsigntool/hap/provider/LocalJKSSignProvider.java | 2 +- .../java/com/ohos/hapsigntool/hap/provider/SignProvider.java | 2 +- .../main/java/com/ohos/hapsigntool/hap/verify/VerifyHap.java | 2 +- .../src/main/java/com/ohos/hapsigntool/utils/HapUtils.java | 2 +- 5 files changed, 4 insertions(+), 9 deletions(-) diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/api/model/Options.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/api/model/Options.java index 3b9cd88b..855d6677 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/api/model/Options.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/api/model/Options.java @@ -235,11 +235,6 @@ public class Options extends HashMap { */ public static final String VALIDITY = "validity"; - /** - * The code sign params of resign hap - */ - public static final String PARAM_CODE_SIGN = "codesign"; - /** * All usages included in the extended key usage. */ diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/provider/LocalJKSSignProvider.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/provider/LocalJKSSignProvider.java index efca0a9a..f7e460c3 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/provider/LocalJKSSignProvider.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/provider/LocalJKSSignProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2022 Huawei Device Co., Ltd. + * Copyright (c) 2021-2023 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at 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 15656d66..5fa44c57 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/provider/SignProvider.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/provider/SignProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2022 Huawei Device Co., Ltd. + * Copyright (c) 2021-2023 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/verify/VerifyHap.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/verify/VerifyHap.java index aad7031c..288481ad 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/verify/VerifyHap.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/verify/VerifyHap.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2022 Huawei Device Co., Ltd. + * Copyright (c) 2021-2023 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/HapUtils.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/HapUtils.java index 615fd675..dc771350 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/HapUtils.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/HapUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2022 Huawei Device Co., Ltd. + * Copyright (c) 2021-2023 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at -- Gitee From bc21325a5b49ee1cb7f2172bd72d4dd11bf82151 Mon Sep 17 00:00:00 2001 From: zhanzeyi Date: Wed, 1 Nov 2023 10:39:45 +0800 Subject: [PATCH 12/15] elf sign --- .../com/ohos/hapsigntool/HapSignTool.java | 6 +- .../hap_sign_tool/src/main/resources/help.txt | 2 +- .../hapsigntool/api/SignToolServiceImpl.java | 4 +- .../hapsigntool/hap/entity/HwBlockHead.java | 23 +- .../hapsigntool/hap/entity/HwSignHead.java | 33 ++- .../hapsigntool/hap/entity/SignBlockData.java | 93 +++++++ .../hap/provider/SignProvider.java | 37 +++ .../ohos/hapsigntool/hap/sign/SignBin.java | 145 +++++++--- .../ohos/hapsigntool/hap/sign/SignElf.java | 261 ++++++++++++++++++ .../ohos/hapsigntool/key/KeyPairTools.java | 6 +- .../hapsigntool/keystore/KeyStoreHelper.java | 3 +- .../hapsigntool/profile/VerifyHelper.java | 4 +- .../hapsigntool/utils/CertificateUtils.java | 4 +- .../com/ohos/hapsigntool/utils/FileUtils.java | 5 +- .../hapsigntool/utils/ParamConstants.java | 7 +- 15 files changed, 566 insertions(+), 67 deletions(-) create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/SignBlockData.java create mode 100644 hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/sign/SignElf.java diff --git a/hapsigntool/hap_sign_tool/src/main/java/com/ohos/hapsigntool/HapSignTool.java b/hapsigntool/hap_sign_tool/src/main/java/com/ohos/hapsigntool/HapSignTool.java index ab4c6cdb..0d7cd776 100644 --- a/hapsigntool/hap_sign_tool/src/main/java/com/ohos/hapsigntool/HapSignTool.java +++ b/hapsigntool/hap_sign_tool/src/main/java/com/ohos/hapsigntool/HapSignTool.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2022 Huawei Device Co., Ltd. + * Copyright (c) 2021-2023 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -277,7 +277,7 @@ public final class HapSignTool { } checkProfile(params); String inForm = params.getString(Options.IN_FORM); - if (!StringUtils.isEmpty(inForm) && !"zip".equalsIgnoreCase(inForm) && !"bin".equalsIgnoreCase(inForm)) { + if (!StringUtils.isEmpty(inForm) && !"zip".equalsIgnoreCase(inForm) && !"bin".equalsIgnoreCase(inForm) && !"elf".equalsIgnoreCase(inForm)) { CustomException.throwException(ERROR.NOT_SUPPORT_ERROR, "inForm params is incorrect"); } String signAlg = params.getString(Options.SIGN_ALG); @@ -321,7 +321,7 @@ public final class HapSignTool { private static boolean runVerifyApp(Options params, ServiceApi api) { params.required(Options.IN_FILE, Options.OUT_CERT_CHAIN, Options.OUT_PROFILE); - FileUtils.validFileType(params.getString(Options.IN_FILE), "hap", "bin"); + FileUtils.validFileType(params.getString(Options.IN_FILE), "hap"); FileUtils.validFileType(params.getString(Options.OUT_CERT_CHAIN), "cer"); FileUtils.validFileType(params.getString(Options.OUT_PROFILE), "p7b"); return api.verifyHap(params); diff --git a/hapsigntool/hap_sign_tool/src/main/resources/help.txt b/hapsigntool/hap_sign_tool/src/main/resources/help.txt index 0aee6ffe..216713af 100644 --- a/hapsigntool/hap_sign_tool/src/main/resources/help.txt +++ b/hapsigntool/hap_sign_tool/src/main/resources/help.txt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2022 Huawei Device Co., Ltd. + * Copyright (c) 2021-2023 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/api/SignToolServiceImpl.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/api/SignToolServiceImpl.java index ff79b211..7e2fd109 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/api/SignToolServiceImpl.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/api/SignToolServiceImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2022 Huawei Device Co., Ltd. + * Copyright (c) 2021-2023 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -322,6 +322,8 @@ public class SignToolServiceImpl implements ServiceApi { String inForm = options.getString(Options.IN_FORM, "zip"); if ("zip".equalsIgnoreCase(inForm)) { return signProvider.sign(options); + } else if ("elf".equalsIgnoreCase(inForm)) { + return signProvider.signElf(options); } else { return signProvider.signBin(options); } diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/HwBlockHead.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/HwBlockHead.java index ba283115..cd10e900 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/HwBlockHead.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/HwBlockHead.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 Huawei Device Co., Ltd. + * Copyright (c) 2022-2023 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -15,6 +15,9 @@ package com.ohos.hapsigntool.hap.entity; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + /** * define class of hap signature sub-block head */ @@ -55,4 +58,22 @@ public class HwBlockHead { (byte) (offset & 0xff) }; } + + /** + * get serialization of HwBlockHead + * + * @param type type of signature block + * @param tag tags of signature block + * @param length the length of block data + * @param offset Byte offset of the data block relative to the start position of the signature block + * @return Byte array after serialization of HwBlockHead + */ + public static byte[] getBlockHeadLittleEndian(char type, char tag, short length, int offset) { + ByteBuffer bf = ByteBuffer.allocate(HwBlockHead.BLOCK_LEN).order(ByteOrder.LITTLE_ENDIAN); + bf.put((byte) (type)); + bf.put((byte) (tag)); + bf.putShort(length); + bf.putInt(offset); + return bf.array(); + } } \ No newline at end of file diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/HwSignHead.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/HwSignHead.java index 6584253b..b85dff25 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/HwSignHead.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/HwSignHead.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2022 Huawei Device Co., Ltd. + * Copyright (c) 2021-2023 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -18,6 +18,8 @@ package com.ohos.hapsigntool.hap.entity; import com.ohos.hapsigntool.utils.ByteArrayUtils; import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; /** * define class of hap signature block head @@ -42,8 +44,7 @@ public class HwSignHead { * @param subBlockSize the total size of all sub-blocks * @return Byte array after serialization of HwSignHead */ - public byte[] getSignHead(int subBlockSize) { - int size = subBlockSize; // total size of sub-block + public byte[] getSignHead(int subBlockSize, int subBlockNum) { byte[] signHead = new byte[SIGN_HEAD_LEN]; int start = 0; try { @@ -55,11 +56,11 @@ public class HwSignHead { if (start < 0) { throw new IOException(); } - start = ByteArrayUtils.insertIntToByteArray(signHead, start, size); + start = ByteArrayUtils.insertIntToByteArray(signHead, start, subBlockSize); if (start < 0) { throw new IOException(); } - start = ByteArrayUtils.insertIntToByteArray(signHead, start, NUM_OF_BLOCK); + start = ByteArrayUtils.insertIntToByteArray(signHead, start, subBlockNum); if (start < 0) { throw new IOException(); } @@ -72,4 +73,26 @@ public class HwSignHead { } return signHead; } + + /** + * get serialization of HwSignHead + * + * @param subBlockSize the total size of all sub-blocks + * @return Byte array after serialization of HwSignHead + */ + public byte[] getSignHeadLittleEndian(int subBlockSize, int subBlockNum) { + ByteBuffer bf = ByteBuffer.allocate(SIGN_HEAD_LEN).order(ByteOrder.LITTLE_ENDIAN); + for (char c : MAGIC) { + bf.put((byte) c); + } + for (char c : VERSION) { + bf.put((byte) c); + } + bf.putInt(subBlockSize); + bf.putInt(subBlockNum); + for (char c : reserve) { + bf.put((byte) c); + } + return bf.array(); + } } diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/SignBlockData.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/SignBlockData.java new file mode 100644 index 00000000..0fd40211 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/SignBlockData.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ohos.hapsigntool.hap.entity; + +import com.ohos.hapsigntool.utils.FileUtils; + +public class SignBlockData { + private char type; + private byte[] blockHead; + private byte[] signData; + private String signFile; + private long len; + private boolean isByte; + + public char getType() { + return type; + } + + public void setType(char type) { + this.type = type; + } + + public byte[] getBlockHead() { + return blockHead; + } + + public void setBlockHead(byte[] blockHead) { + this.blockHead = blockHead; + } + + public byte[] getSignData() { + return signData; + } + + public void setSignData(byte[] signData) { + this.signData = signData; + } + + public String getSignFile() { + return signFile; + } + + public void setSignFile(String signFile) { + this.signFile = signFile; + } + + public long getLen() { + return len; + } + + public void setLen(long len) { + this.len = len; + } + + public boolean isByte() { + return isByte; + } + + public void setByte(boolean aByte) { + isByte = aByte; + } + + public SignBlockData() { + + } + + public SignBlockData(byte[] signData, char type) { + this.signData = signData; + this.type = type; + this.len = signData == null ? 0 : signData.length; + this.isByte = true; + } + + public SignBlockData(String signFile, char type) { + this.signFile = signFile; + this.type = type; + this.len = FileUtils.getFileLen(signFile); + this.isByte = false; + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/provider/SignProvider.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/provider/SignProvider.java index 5fa44c57..ca51c18f 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 @@ -32,7 +32,9 @@ import com.ohos.hapsigntool.hap.exception.MissingParamsException; import com.ohos.hapsigntool.hap.exception.ProfileException; import com.ohos.hapsigntool.hap.exception.SignatureException; import com.ohos.hapsigntool.hap.exception.VerifyCertificateChainException; +import com.ohos.hapsigntool.hap.exception.HapFormatException; import com.ohos.hapsigntool.hap.sign.SignBin; +import com.ohos.hapsigntool.hap.sign.SignElf; import com.ohos.hapsigntool.hap.sign.SignHap; import com.ohos.hapsigntool.hap.sign.SignatureAlgorithm; import com.ohos.hapsigntool.hap.verify.VerifyUtils; @@ -51,6 +53,7 @@ import com.ohos.hapsigntool.zip.ZipDataInput; import com.ohos.hapsigntool.zip.ZipDataOutput; import com.ohos.hapsigntool.zip.ZipFileInfo; import com.ohos.hapsigntool.zip.ZipUtils; + import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.bouncycastle.cms.CMSException; @@ -79,6 +82,7 @@ import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.TimeZone; +import java.util.Optional; import java.util.jar.JarFile; import java.util.jar.JarOutputStream; @@ -264,6 +268,39 @@ public abstract class SignProvider { return true; } + /** + * sign elf file + * + * @param options parameters used to sign elf file + * @return true, if sign successfully. + */ + public boolean signElf(Options options) { + Security.addProvider(new BouncyCastleProvider()); + List publicCert = null; + SignerConfig signerConfig; + try { + publicCert = getX509Certificates(options); + + // Get x509 CRL + Optional crl = getCrl(); + + // Create signer configs, which contains public cert and crl info. + signerConfig = createSignerConfigs(publicCert, crl, options); + } catch (InvalidKeyException | InvalidParamsException | MissingParamsException | ProfileException e) { + LOGGER.error("create signer configs failed.", e); + printErrorLogWithoutStack(e); + return false; + } + + /* 6. make signed file into output file. */ + if (!SignElf.sign(signerConfig, signParams)) { + LOGGER.error("hap-sign-tool: error: Sign elf internal failed."); + return false; + } + LOGGER.info("Sign success"); + return true; + } + /** * sign hap file * diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/sign/SignBin.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/sign/SignBin.java index bc94668d..00768a85 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/sign/SignBin.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/sign/SignBin.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2022 Huawei Device Co., Ltd. + * Copyright (c) 2021-2023 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -15,12 +15,17 @@ package com.ohos.hapsigntool.hap.sign; +import com.ohos.hapsigntool.codesigning.exception.CodeSignException; +import com.ohos.hapsigntool.codesigning.exception.FsVerityDigestException; +import com.ohos.hapsigntool.codesigning.sign.CodeSigning; import com.ohos.hapsigntool.hap.config.SignerConfig; import com.ohos.hapsigntool.hap.entity.HwBlockHead; import com.ohos.hapsigntool.hap.entity.HwSignHead; +import com.ohos.hapsigntool.hap.entity.SignBlockData; import com.ohos.hapsigntool.hap.entity.SignContentInfo; import com.ohos.hapsigntool.hap.entity.SignatureBlockTags; import com.ohos.hapsigntool.hap.entity.SignatureBlockTypes; +import com.ohos.hapsigntool.hap.exception.HapFormatException; import com.ohos.hapsigntool.hap.exception.SignatureException; import com.ohos.hapsigntool.utils.FileUtils; import com.ohos.hapsigntool.utils.HashUtils; @@ -34,6 +39,8 @@ import java.io.DataOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; import java.util.Map; /** @@ -50,6 +57,12 @@ public class SignBin { private static final Logger LOGGER = LogManager.getLogger(SignBin.class); + private static final String CODESIGN_ON = "1"; + + private static final char CODESIGN_BLOCK_TYPE = 3; + + private static int blockNum = 0; + /** * Sign the bin file. * @@ -64,7 +77,7 @@ public class SignBin { String outputFile = signParams.get(ParamConstants.PARAM_BASIC_OUTPUT_FILE); String profileFile = signParams.get(ParamConstants.PARAM_BASIC_PROFILE); String profileSigned = signParams.get(ParamConstants.PARAM_BASIC_PROFILE_SIGNED); - if (!writeBlockDataToFile(inputFile, outputFile, profileFile, profileSigned)) { + if (!writeBlockDataToFile(signerConfig, inputFile, outputFile, profileFile, profileSigned, signParams)) { LOGGER.error("The block head data made failed."); ParamProcessUtil.delDir(new File(outputFile)); return false; @@ -81,7 +94,7 @@ public class SignBin { LOGGER.info("The data signed success."); /* 3. Make sign data, and write to output file */ - if (!writeSignHeadDataToOutputFile(inputFile, outputFile)) { + if (!writeSignHeadDataToOutputFile(inputFile, outputFile, blockNum)) { LOGGER.error("The sign head data made failed."); ParamProcessUtil.delDir(new File(outputFile)); } else { @@ -90,68 +103,70 @@ public class SignBin { return result; } - private static boolean writeBlockDataToFile( - String inputFile, String outputFile, String profileFile, String profileSigned) { + private static boolean writeBlockDataToFile(SignerConfig signerConfig, + String inputFile, String outputFile, String profileFile, String profileSigned, Map signParams) { try { + List signDataList = new ArrayList<>(); + long binFileLen = FileUtils.getFileLen(inputFile); - long profileDataLen = FileUtils.getFileLen(profileFile); - if (!checkBinAndProfileLengthIsValid(binFileLen, profileDataLen)) { - LOGGER.error("file length is invalid, binFileLen: " + binFileLen - + " profileDataLen: " + profileDataLen); + if (binFileLen == -1) { + LOGGER.error("file length is invalid, bin file len: " + binFileLen); throw new IOException(); } + // 1. generate sign data + signDataList.add(generateProfileSignByte(profileFile, profileSigned)); + signDataList.add(generateSignByte()); - long offset = binFileLen + HwBlockHead.getBlockLen() + HwBlockHead.getBlockLen(); - if (isLongOverflowInteger(offset)) { - LOGGER.error("The profile block head offset is overflow interger range, offset: " + offset); - throw new IOException(); + blockNum = signDataList.size() + 1; + SignBlockData codeSign = generateCodeSignByte(signerConfig, signParams, inputFile, blockNum, binFileLen); + if (codeSign != null) { + signDataList.add(0, codeSign); + blockNum += 1; } - char isSigned = SignatureBlockTypes.getProfileBlockTypes(profileSigned); - byte[] proBlockByte = - HwBlockHead.getBlockHead(isSigned, SignatureBlockTags.DEFAULT, (short) profileDataLen, (int) offset); + // 2. use sign data generate offset and sign block head - offset += profileDataLen; - if (isLongOverflowInteger(offset)) { - LOGGER.error("The sign block head offset is overflow integer range, offset: " + offset); - throw new IOException(); - } - byte[] signBlockByte = HwBlockHead.getBlockHead( - SignatureBlockTypes.SIGNATURE_BLOCK, SignatureBlockTags.DEFAULT, (short) 0, (int) offset); + generateSignBlockHead(signDataList, binFileLen); - return writeSignedBin(inputFile, proBlockByte, signBlockByte, profileFile, outputFile); + return writeSignedBin(inputFile, signDataList, outputFile); } catch (IOException e) { LOGGER.error("writeBlockDataToFile failed.", e); return false; + } catch (FsVerityDigestException | CodeSignException | HapFormatException e) { + throw new RuntimeException(e); } } - private static boolean writeSignedBin(String inputFile, byte[] proBlockByte, byte[] signBlockByte, - String profileFile, String outputFile) { + private static boolean writeSignedBin(String inputFile, List signBlockList, String outputFile) { try (FileOutputStream fileOutputStream = new FileOutputStream(outputFile); - DataOutputStream dataOutputStream = new DataOutputStream(fileOutputStream);) { + DataOutputStream dataOutputStream = new DataOutputStream(fileOutputStream)) { // 1. write the input file to the output file. if (!FileUtils.writeFileToDos(inputFile, dataOutputStream)) { - LOGGER.error("Failed to write infomation of input file: " + inputFile + + LOGGER.error("Failed to write information of input file: " + inputFile + " to outputFile: " + outputFile); throw new IOException(); } - // 2. write profile block head to the output file. - if (!FileUtils.writeByteToDos(proBlockByte, dataOutputStream)) { - LOGGER.error("Failed to write proBlockByte to output file: " + outputFile); - throw new IOException(); + // 2. write block head to the output file. + for (SignBlockData signBlockData : signBlockList) { + if (!FileUtils.writeByteToDos(signBlockData.getBlockHead(), dataOutputStream)) { + LOGGER.error("Failed to write Block Head to output file: " + outputFile); + throw new IOException(); + } } - // 3. write sign block head to the output file. - if (!FileUtils.writeByteToDos(signBlockByte, dataOutputStream)) { - LOGGER.error("Failed to write binBlockByte to output file: " + outputFile); - throw new IOException(); - } + // 3. write block data to the output file. + for (SignBlockData signBlockData : signBlockList) { + boolean writeFlag; + if (signBlockData.isByte()) { + writeFlag = FileUtils.writeByteToDos(signBlockData.getSignData(), dataOutputStream); + } else { + writeFlag = FileUtils.writeFileToDos(signBlockData.getSignFile(), dataOutputStream); + } - // 4. write profile src file to the output file. - if (!FileUtils.writeFileToDos(profileFile, dataOutputStream)) { - LOGGER.error("Failed to write profile file: " + profileFile); - throw new IOException(); + if (!writeFlag) { + LOGGER.error("Failed to write Block Data to output file: " + outputFile); + throw new IOException(); + } } } catch (IOException e) { LOGGER.error("writeSignedBin failed.", e); @@ -160,18 +175,58 @@ public class SignBin { return true; } - private static boolean checkBinAndProfileLengthIsValid(long binFileLen, long profileDataLen) { - return (binFileLen != -1) && (profileDataLen != -1) && (!isLongOverflowShort(profileDataLen)); + private static void generateSignBlockHead(List signDataList, long binFileLen) + throws IOException { + long offset = binFileLen + (long) HwBlockHead.getBlockLen() * signDataList.size(); + + for (int i = 0; i < signDataList.size(); i++) { + SignBlockData signBlockData = signDataList.get(i); + signBlockData.setBlockHead(HwBlockHead.getBlockHead(signBlockData.getType(), SignatureBlockTags.DEFAULT, + (short) signBlockData.getLen(), (int) offset)); + offset += signBlockData.getLen(); + if (isLongOverflowInteger(offset)) { + LOGGER.error("The sign block " + i + "offset is overflow integer, offset: " + offset); + throw new IOException(); + } + } + } + + private static SignBlockData generateProfileSignByte(String profileFile, String profileSigned) throws IOException { + long profileDataLen = FileUtils.getFileLen(profileFile); + + if (profileDataLen == -1 || isLongOverflowShort(profileDataLen)) { + LOGGER.error("file length is invalid, profileDataLen: " + profileDataLen); + throw new IOException(); + } + + char isSigned = SignatureBlockTypes.getProfileBlockTypes(profileSigned); + return new SignBlockData(profileFile, isSigned); + } + + private static SignBlockData generateCodeSignByte(SignerConfig signerConfig, Map signParams, String inputFile, + int blockNum, long binFileLen) throws IOException, FsVerityDigestException, CodeSignException, HapFormatException { + if (CODESIGN_ON.equals(signParams.get(ParamConstants.PARAM_CODE_SIGN))) { + CodeSigning codeSigning = new CodeSigning(signerConfig); + long offset = binFileLen + (long) HwBlockHead.getBlockLen() * blockNum; + byte[] codesignData = codeSigning.getCodeSignBlock(new File(inputFile), offset, "bin"); + return new SignBlockData(codesignData, CODESIGN_BLOCK_TYPE); + } + return null; } - private static boolean writeSignHeadDataToOutputFile(String inputFile, String outputFile) { + private static SignBlockData generateSignByte() { + return new SignBlockData((byte[]) null, SignatureBlockTypes.SIGNATURE_BLOCK); + } + + + private static boolean writeSignHeadDataToOutputFile(String inputFile, String outputFile, int blockNum) { long size = FileUtils.getFileLen(outputFile) - FileUtils.getFileLen(inputFile) + HwSignHead.SIGN_HEAD_LEN; if (isLongOverflowInteger(size)) { LOGGER.error("File size is Overflow integer range."); return false; } HwSignHead signHeadData = new HwSignHead(); - byte[] signHeadByte = signHeadData.getSignHead((int) size); + byte[] signHeadByte = signHeadData.getSignHead((int) size, blockNum); if (signHeadByte == null) { LOGGER.error("Failed to get sign head data."); return false; diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/sign/SignElf.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/sign/SignElf.java new file mode 100644 index 00000000..b5a88b56 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/sign/SignElf.java @@ -0,0 +1,261 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ohos.hapsigntool.hap.sign; + +import com.ohos.hapsigntool.codesigning.exception.CodeSignException; +import com.ohos.hapsigntool.codesigning.exception.FsVerityDigestException; +import com.ohos.hapsigntool.codesigning.sign.CodeSigning; +import com.ohos.hapsigntool.hap.config.SignerConfig; +import com.ohos.hapsigntool.hap.entity.HwBlockHead; +import com.ohos.hapsigntool.hap.entity.HwSignHead; +import com.ohos.hapsigntool.hap.entity.SignBlockData; +import com.ohos.hapsigntool.hap.entity.SignContentInfo; +import com.ohos.hapsigntool.hap.entity.SignatureBlockTags; +import com.ohos.hapsigntool.hap.entity.SignatureBlockTypes; +import com.ohos.hapsigntool.hap.exception.HapFormatException; +import com.ohos.hapsigntool.hap.exception.SignatureException; +import com.ohos.hapsigntool.utils.FileUtils; +import com.ohos.hapsigntool.utils.HashUtils; +import com.ohos.hapsigntool.utils.ParamConstants; +import com.ohos.hapsigntool.utils.ParamProcessUtil; +import com.ohos.hapsigntool.utils.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * LiteOS bin file Signature signer. + * + * @since 2021/12/21 + */ +public class SignElf { + /** + * Constructor of Method + */ + private SignElf() { + } + + private static final Logger LOGGER = LogManager.getLogger(SignElf.class); + + private static final String CODESIGN_ON = "1"; + + private static final char CODESIGN_BLOCK_TYPE = 3; + + private static int blockNum = 0; + + /** + * Sign the bin file. + * + * @param signerConfig Config of the bin file to be signed. + * @param signParams The input parameters of sign bin. + * @return true if sign successfully; false otherwise. + */ + public static boolean sign(SignerConfig signerConfig, Map signParams) { + boolean result = false; + /* 1. Make block head, write to output file. */ + String inputFile = signParams.get(ParamConstants.PARAM_BASIC_INPUT_FILE); + String outputFile = signParams.get(ParamConstants.PARAM_BASIC_OUTPUT_FILE); + String profileFile = signParams.get(ParamConstants.PARAM_BASIC_PROFILE); + String profileSigned = signParams.get(ParamConstants.PARAM_BASIC_PROFILE_SIGNED); + if (!writeBlockDataToFile(signerConfig, inputFile, outputFile, profileFile, profileSigned, signParams)) { + LOGGER.error("The block head data made failed."); + ParamProcessUtil.delDir(new File(outputFile)); + return false; + } + LOGGER.info("The block head data made success."); + + /* 2. Make sign data, and write to output file */ + String signAlg = signParams.get(ParamConstants.PARAM_BASIC_SIGANTURE_ALG); + if (!writeSignDataToOutputFile(signerConfig, outputFile, signAlg)) { + LOGGER.error("The sign data made failed."); + ParamProcessUtil.delDir(new File(outputFile)); + return false; + } + LOGGER.info("The data signed success."); + + /* 3. Make sign data, and write to output file */ + if (!writeSignHeadDataToOutputFile(inputFile, outputFile, blockNum)) { + LOGGER.error("The sign head data made failed."); + ParamProcessUtil.delDir(new File(outputFile)); + } else { + result = true; + } + return result; + } + + private static boolean writeBlockDataToFile(SignerConfig signerConfig, + String inputFile, String outputFile, String profileFile, String profileSigned, Map signParams) { + try { + List signDataList = new ArrayList<>(); + + long binFileLen = FileUtils.getFileLen(inputFile); + if (binFileLen == -1) { + LOGGER.error("file length is invalid, bin file len: " + binFileLen); + throw new IOException(); + } + // 1. generate sign data + if (!StringUtils.isEmpty(signParams.get(ParamConstants.PARAM_BASIC_PROFILE))) { + signDataList.add(generateProfileSignByte(profileFile, profileSigned)); + } + blockNum = signDataList.size(); + SignBlockData codeSign = generateCodeSignByte(signerConfig, signParams, inputFile, blockNum, binFileLen); + if (codeSign != null) { + signDataList.add(0, codeSign); + } + blockNum = signDataList.size(); + // 2. use sign data generate offset and sign block head + generateSignBlockHead(signDataList); + + return writeSignedElf(inputFile, signDataList, outputFile); + } catch (IOException e) { + LOGGER.error("writeBlockDataToFile failed.", e); + return false; + } catch (FsVerityDigestException | CodeSignException | HapFormatException e) { + throw new RuntimeException(e); + } + } + + private static boolean writeSignedElf(String inputFile, List signBlockList, String outputFile) { + try (FileOutputStream fileOutputStream = new FileOutputStream(outputFile); + DataOutputStream dataOutputStream = new DataOutputStream(fileOutputStream)) { + // 1. write the input file to the output file. + if (!FileUtils.writeFileToDos(inputFile, dataOutputStream)) { + LOGGER.error("Failed to write information of input file: " + inputFile + + " to outputFile: " + outputFile); + throw new IOException(); + } + + // 2. write block head to the output file. + for (SignBlockData signBlockData : signBlockList) { + if (!FileUtils.writeByteToDos(signBlockData.getBlockHead(), dataOutputStream)) { + LOGGER.error("Failed to write Block Head to output file: " + outputFile); + throw new IOException(); + } + } + + // 3. write block data to the output file. + for (SignBlockData signBlockData : signBlockList) { + boolean writeFlag; + if (signBlockData.isByte()) { + writeFlag = FileUtils.writeByteToDos(signBlockData.getSignData(), dataOutputStream); + } else { + writeFlag = FileUtils.writeFileToDos(signBlockData.getSignFile(), dataOutputStream); + } + + if (!writeFlag) { + LOGGER.error("Failed to write Block Data to output file: " + outputFile); + throw new IOException(); + } + } + } catch (IOException e) { + LOGGER.error("writeSignedBin failed.", e); + return false; + } + return true; + } + + private static void generateSignBlockHead(List signDataList) + throws IOException { + long offset = (long) HwBlockHead.getBlockLen() * signDataList.size(); + + for (int i = 0; i < signDataList.size(); i++) { + SignBlockData signBlockData = signDataList.get(i); + + signBlockData.setBlockHead(HwBlockHead.getBlockHeadLittleEndian(signBlockData.getType(), SignatureBlockTags.DEFAULT, + (short) signBlockData.getLen(), (int) offset)); + offset += signBlockData.getLen(); + if (isLongOverflowInteger(offset)) { + LOGGER.error("The sign block " + i + "offset is overflow integer, offset: " + offset); + throw new IOException(); + } + } + } + + private static SignBlockData generateProfileSignByte(String profileFile, String profileSigned) throws IOException { + long profileDataLen = FileUtils.getFileLen(profileFile); + + if (profileDataLen == -1 || isLongOverflowShort(profileDataLen)) { + LOGGER.error("file length is invalid, profileDataLen: " + profileDataLen); + throw new IOException(); + } + + char isSigned = SignatureBlockTypes.getProfileBlockTypes(profileSigned); + return new SignBlockData(profileFile, isSigned); + } + + private static SignBlockData generateCodeSignByte(SignerConfig signerConfig, Map signParams, String inputFile, + int blockNum, long binFileLen) throws IOException, FsVerityDigestException, CodeSignException, HapFormatException { + if (CODESIGN_ON.equals(signParams.get(ParamConstants.PARAM_CODE_SIGN))) { + CodeSigning codeSigning = new CodeSigning(signerConfig); + long offset = binFileLen + (long) HwBlockHead.getBlockLen() * blockNum; + byte[] codesignData = codeSigning.getCodeSignBlock(new File(inputFile), offset, signParams.get(ParamConstants.PARAM_IN_FORM)); + return new SignBlockData(codesignData, CODESIGN_BLOCK_TYPE); + } + return null; + } + + private static boolean writeSignHeadDataToOutputFile(String inputFile, String outputFile, int blockNum) { + long size = FileUtils.getFileLen(outputFile) - FileUtils.getFileLen(inputFile); + if (isLongOverflowInteger(size)) { + LOGGER.error("File size is Overflow integer range."); + return false; + } + HwSignHead signHeadData = new HwSignHead(); + byte[] signHeadByte = signHeadData.getSignHeadLittleEndian((int) size, blockNum); + if (signHeadByte == null) { + LOGGER.error("Failed to get sign head data."); + return false; + } + return FileUtils.writeByteToOutFile(signHeadByte, outputFile); + } + + private static boolean writeSignDataToOutputFile(SignerConfig signerConfig, String outputFile, String signAlg) { + String alg = ParamProcessUtil.getSignatureAlgorithm(signAlg).getContentDigestAlgorithm().getDigestAlgorithm(); + byte[] data = HashUtils.getFileDigest(outputFile, alg); + if (data == null) { + LOGGER.error("getFileDigest failed."); + return false; + } + byte[] outputChunk = null; + SignContentInfo contentInfo = null; + try { + contentInfo = new SignContentInfo(); + contentInfo.addContentHashData( + (char) 0, SignatureBlockTags.HASH_ROOT_4K, (short) HashUtils.getHashAlgsId(alg), data.length, data); + byte[] dig = contentInfo.getByteContent(); + outputChunk = Pkcs7Generator.BC.generateSignedData(dig, signerConfig); + return FileUtils.writeByteToOutFile(outputChunk, outputFile); + } catch (SignatureException e) { + LOGGER.error("Sign hap Lite failed.", e); + } + return false; + } + + private static boolean isLongOverflowInteger(long num) { + return (num - (num & 0xffffffffL)) != 0; + } + + private static boolean isLongOverflowShort(long num) { + return (num - (num & 0xffffL)) != 0; + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/key/KeyPairTools.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/key/KeyPairTools.java index 4197932f..ce5b5119 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/key/KeyPairTools.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/key/KeyPairTools.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2022 Huawei Device Co., Ltd. + * Copyright (c) 2021-2023 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -27,9 +27,9 @@ import java.security.Key; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; -import java.security.PublicKey; -import java.security.PrivateKey; import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/keystore/KeyStoreHelper.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/keystore/KeyStoreHelper.java index 65ce9435..422a04d9 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/keystore/KeyStoreHelper.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/keystore/KeyStoreHelper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2022 Huawei Device Co., Ltd. + * Copyright (c) 2021-2023 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -32,7 +32,6 @@ import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder; import org.bouncycastle.operator.ContentSigner; import java.io.FileInputStream; -import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.security.KeyPair; diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/profile/VerifyHelper.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/profile/VerifyHelper.java index 71d691cd..38139a56 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/profile/VerifyHelper.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/profile/VerifyHelper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2022 Huawei Device Co., Ltd. + * Copyright (c) 2021-2023 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -54,9 +54,9 @@ import java.time.LocalDateTime; import java.time.ZoneId; import java.util.ArrayList; import java.util.Collection; +import java.util.Date; import java.util.Iterator; import java.util.List; -import java.util.Date; /** * Signed provision profile verifier. diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/CertificateUtils.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/CertificateUtils.java index 96abd929..eda1116f 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/CertificateUtils.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/CertificateUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 Huawei Device Co., Ltd. + * Copyright (c) 2022-2023 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -27,8 +27,8 @@ import java.security.SignatureException; import java.security.cert.CertificateException; import java.security.cert.CertificateExpiredException; import java.security.cert.CertificateFactory; -import java.security.cert.X509Certificate; import java.security.cert.CertificateNotYetValidException; +import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/FileUtils.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/FileUtils.java index cc8d917f..cd73cf2d 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/FileUtils.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/FileUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2022 Huawei Device Co., Ltd. + * Copyright (c) 2021-2023 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -221,6 +221,9 @@ public final class FileUtils { * @return true, if write successfully. */ public static boolean writeByteToDos(byte[] data, DataOutputStream dos) { + if (data == null) { + return true; + } try { dos.write(data); } catch (IOException e) { diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/ParamConstants.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/ParamConstants.java index bf066bd7..6d334536 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/ParamConstants.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/ParamConstants.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2022 Huawei Device Co., Ltd. + * Copyright (c) 2021-2023 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -236,6 +236,11 @@ public class ParamConstants { */ public static final String PARAM_RESIGN_CONFIG_FILE = "resignconfig"; + /** + * sign file type bin or zip or elf + */ + public static final String PARAM_IN_FORM = "inForm"; + /** * The code sign params of resign hap */ -- Gitee From 398b7199c998cac95b8cf16e9cd3f6c92b5cbde8 Mon Sep 17 00:00:00 2001 From: yangmingming Date: Wed, 1 Nov 2023 10:58:18 +0800 Subject: [PATCH 13/15] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E7=89=88=E6=9C=AC?= =?UTF-8?q?=E5=B9=B4=E4=BB=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: yangmingming --- .../src/main/java/com/ohos/hapsigntool/hap/sign/SignHap.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/sign/SignHap.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/sign/SignHap.java index 8f223a1d..219a5898 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/sign/SignHap.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/sign/SignHap.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2022 Huawei Device Co., Ltd. + * Copyright (c) 2021-2023 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at -- Gitee From 59fecd08f034afb7d707a9bc7a2402be4da21bcd Mon Sep 17 00:00:00 2001 From: zhanzeyi Date: Wed, 1 Nov 2023 11:24:56 +0800 Subject: [PATCH 14/15] elf sign --- .../java/com/ohos/hapsigntool/api/SignToolServiceImpl.java | 4 +--- .../java/com/ohos/hapsigntool/hap/provider/SignProvider.java | 3 ++- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/api/SignToolServiceImpl.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/api/SignToolServiceImpl.java index 7e2fd109..a0a01657 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/api/SignToolServiceImpl.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/api/SignToolServiceImpl.java @@ -44,10 +44,8 @@ import java.security.Security; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; import java.util.Arrays; +import java.util.List; /** * Main entry of lib. 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 ca51c18f..227ed9fe 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 @@ -669,7 +669,8 @@ public abstract class SignProvider { ParamConstants.PARAM_BASIC_PROFILE_SIGNED, ParamConstants.PARAM_LOCAL_PUBLIC_CERT, ParamConstants.PARAM_BASIC_COMPATIBLE_VERSION, - ParamConstants.PARAM_CODE_SIGN + ParamConstants.PARAM_CODE_SIGN, + ParamConstants.PARAM_IN_FORM }; Set paramSet = ParamProcessUtil.initParamField(paramFileds); -- Gitee From 744f051d753448c6f352cb4beeb564c161f34da6 Mon Sep 17 00:00:00 2001 From: zhanzeyi Date: Fri, 3 Nov 2023 11:47:04 +0800 Subject: [PATCH 15/15] elf sign without bin sign --- .../hapsigntool/hap/entity/HwSignHead.java | 2 +- .../ohos/hapsigntool/hap/sign/SignBin.java | 145 ++++++------------ .../ohos/hapsigntool/hap/sign/SignElf.java | 9 -- 3 files changed, 46 insertions(+), 110 deletions(-) diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/HwSignHead.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/HwSignHead.java index b85dff25..cc8c17f7 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/HwSignHead.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/HwSignHead.java @@ -34,7 +34,7 @@ public class HwSignHead { private static final char[] MAGIC = "hw signed app ".toCharArray(); // 16Bytes-Magic private static final char[] VERSION = "1000".toCharArray(); // 4-Bytes, version is 1.0.0.0 - private static final int NUM_OF_BLOCK = 2; // number of sub-block + public static final int NUM_OF_BLOCK = 2; // number of sub-block private static final int RESERVE_LENGTH = 4; private char[] reserve = new char[RESERVE_LENGTH]; diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/sign/SignBin.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/sign/SignBin.java index 00768a85..7ad13e40 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/sign/SignBin.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/sign/SignBin.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2023 Huawei Device Co., Ltd. + * Copyright (c) 2021-2022 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -15,17 +15,12 @@ package com.ohos.hapsigntool.hap.sign; -import com.ohos.hapsigntool.codesigning.exception.CodeSignException; -import com.ohos.hapsigntool.codesigning.exception.FsVerityDigestException; -import com.ohos.hapsigntool.codesigning.sign.CodeSigning; import com.ohos.hapsigntool.hap.config.SignerConfig; import com.ohos.hapsigntool.hap.entity.HwBlockHead; import com.ohos.hapsigntool.hap.entity.HwSignHead; -import com.ohos.hapsigntool.hap.entity.SignBlockData; import com.ohos.hapsigntool.hap.entity.SignContentInfo; import com.ohos.hapsigntool.hap.entity.SignatureBlockTags; import com.ohos.hapsigntool.hap.entity.SignatureBlockTypes; -import com.ohos.hapsigntool.hap.exception.HapFormatException; import com.ohos.hapsigntool.hap.exception.SignatureException; import com.ohos.hapsigntool.utils.FileUtils; import com.ohos.hapsigntool.utils.HashUtils; @@ -39,8 +34,6 @@ import java.io.DataOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; -import java.util.ArrayList; -import java.util.List; import java.util.Map; /** @@ -57,12 +50,6 @@ public class SignBin { private static final Logger LOGGER = LogManager.getLogger(SignBin.class); - private static final String CODESIGN_ON = "1"; - - private static final char CODESIGN_BLOCK_TYPE = 3; - - private static int blockNum = 0; - /** * Sign the bin file. * @@ -77,7 +64,7 @@ public class SignBin { String outputFile = signParams.get(ParamConstants.PARAM_BASIC_OUTPUT_FILE); String profileFile = signParams.get(ParamConstants.PARAM_BASIC_PROFILE); String profileSigned = signParams.get(ParamConstants.PARAM_BASIC_PROFILE_SIGNED); - if (!writeBlockDataToFile(signerConfig, inputFile, outputFile, profileFile, profileSigned, signParams)) { + if (!writeBlockDataToFile(inputFile, outputFile, profileFile, profileSigned)) { LOGGER.error("The block head data made failed."); ParamProcessUtil.delDir(new File(outputFile)); return false; @@ -94,7 +81,7 @@ public class SignBin { LOGGER.info("The data signed success."); /* 3. Make sign data, and write to output file */ - if (!writeSignHeadDataToOutputFile(inputFile, outputFile, blockNum)) { + if (!writeSignHeadDataToOutputFile(inputFile, outputFile)) { LOGGER.error("The sign head data made failed."); ParamProcessUtil.delDir(new File(outputFile)); } else { @@ -103,70 +90,68 @@ public class SignBin { return result; } - private static boolean writeBlockDataToFile(SignerConfig signerConfig, - String inputFile, String outputFile, String profileFile, String profileSigned, Map signParams) { + private static boolean writeBlockDataToFile( + String inputFile, String outputFile, String profileFile, String profileSigned) { try { - List signDataList = new ArrayList<>(); - long binFileLen = FileUtils.getFileLen(inputFile); - if (binFileLen == -1) { - LOGGER.error("file length is invalid, bin file len: " + binFileLen); + long profileDataLen = FileUtils.getFileLen(profileFile); + if (!checkBinAndProfileLengthIsValid(binFileLen, profileDataLen)) { + LOGGER.error("file length is invalid, binFileLen: " + binFileLen + + " profileDataLen: " + profileDataLen); throw new IOException(); } - // 1. generate sign data - signDataList.add(generateProfileSignByte(profileFile, profileSigned)); - signDataList.add(generateSignByte()); - blockNum = signDataList.size() + 1; - SignBlockData codeSign = generateCodeSignByte(signerConfig, signParams, inputFile, blockNum, binFileLen); - if (codeSign != null) { - signDataList.add(0, codeSign); - blockNum += 1; + long offset = binFileLen + HwBlockHead.getBlockLen() + HwBlockHead.getBlockLen(); + if (isLongOverflowInteger(offset)) { + LOGGER.error("The profile block head offset is overflow interger range, offset: " + offset); + throw new IOException(); } - // 2. use sign data generate offset and sign block head + char isSigned = SignatureBlockTypes.getProfileBlockTypes(profileSigned); + byte[] proBlockByte = + HwBlockHead.getBlockHead(isSigned, SignatureBlockTags.DEFAULT, (short) profileDataLen, (int) offset); - generateSignBlockHead(signDataList, binFileLen); + offset += profileDataLen; + if (isLongOverflowInteger(offset)) { + LOGGER.error("The sign block head offset is overflow integer range, offset: " + offset); + throw new IOException(); + } + byte[] signBlockByte = HwBlockHead.getBlockHead( + SignatureBlockTypes.SIGNATURE_BLOCK, SignatureBlockTags.DEFAULT, (short) 0, (int) offset); - return writeSignedBin(inputFile, signDataList, outputFile); + return writeSignedBin(inputFile, proBlockByte, signBlockByte, profileFile, outputFile); } catch (IOException e) { LOGGER.error("writeBlockDataToFile failed.", e); return false; - } catch (FsVerityDigestException | CodeSignException | HapFormatException e) { - throw new RuntimeException(e); } } - private static boolean writeSignedBin(String inputFile, List signBlockList, String outputFile) { + private static boolean writeSignedBin(String inputFile, byte[] proBlockByte, byte[] signBlockByte, + String profileFile, String outputFile) { try (FileOutputStream fileOutputStream = new FileOutputStream(outputFile); - DataOutputStream dataOutputStream = new DataOutputStream(fileOutputStream)) { + DataOutputStream dataOutputStream = new DataOutputStream(fileOutputStream);) { // 1. write the input file to the output file. if (!FileUtils.writeFileToDos(inputFile, dataOutputStream)) { - LOGGER.error("Failed to write information of input file: " + inputFile + + LOGGER.error("Failed to write infomation of input file: " + inputFile + " to outputFile: " + outputFile); throw new IOException(); } - // 2. write block head to the output file. - for (SignBlockData signBlockData : signBlockList) { - if (!FileUtils.writeByteToDos(signBlockData.getBlockHead(), dataOutputStream)) { - LOGGER.error("Failed to write Block Head to output file: " + outputFile); - throw new IOException(); - } + // 2. write profile block head to the output file. + if (!FileUtils.writeByteToDos(proBlockByte, dataOutputStream)) { + LOGGER.error("Failed to write proBlockByte to output file: " + outputFile); + throw new IOException(); } - // 3. write block data to the output file. - for (SignBlockData signBlockData : signBlockList) { - boolean writeFlag; - if (signBlockData.isByte()) { - writeFlag = FileUtils.writeByteToDos(signBlockData.getSignData(), dataOutputStream); - } else { - writeFlag = FileUtils.writeFileToDos(signBlockData.getSignFile(), dataOutputStream); - } + // 3. write sign block head to the output file. + if (!FileUtils.writeByteToDos(signBlockByte, dataOutputStream)) { + LOGGER.error("Failed to write binBlockByte to output file: " + outputFile); + throw new IOException(); + } - if (!writeFlag) { - LOGGER.error("Failed to write Block Data to output file: " + outputFile); - throw new IOException(); - } + // 4. write profile src file to the output file. + if (!FileUtils.writeFileToDos(profileFile, dataOutputStream)) { + LOGGER.error("Failed to write profile file: " + profileFile); + throw new IOException(); } } catch (IOException e) { LOGGER.error("writeSignedBin failed.", e); @@ -175,58 +160,18 @@ public class SignBin { return true; } - private static void generateSignBlockHead(List signDataList, long binFileLen) - throws IOException { - long offset = binFileLen + (long) HwBlockHead.getBlockLen() * signDataList.size(); - - for (int i = 0; i < signDataList.size(); i++) { - SignBlockData signBlockData = signDataList.get(i); - signBlockData.setBlockHead(HwBlockHead.getBlockHead(signBlockData.getType(), SignatureBlockTags.DEFAULT, - (short) signBlockData.getLen(), (int) offset)); - offset += signBlockData.getLen(); - if (isLongOverflowInteger(offset)) { - LOGGER.error("The sign block " + i + "offset is overflow integer, offset: " + offset); - throw new IOException(); - } - } - } - - private static SignBlockData generateProfileSignByte(String profileFile, String profileSigned) throws IOException { - long profileDataLen = FileUtils.getFileLen(profileFile); - - if (profileDataLen == -1 || isLongOverflowShort(profileDataLen)) { - LOGGER.error("file length is invalid, profileDataLen: " + profileDataLen); - throw new IOException(); - } - - char isSigned = SignatureBlockTypes.getProfileBlockTypes(profileSigned); - return new SignBlockData(profileFile, isSigned); - } - - private static SignBlockData generateCodeSignByte(SignerConfig signerConfig, Map signParams, String inputFile, - int blockNum, long binFileLen) throws IOException, FsVerityDigestException, CodeSignException, HapFormatException { - if (CODESIGN_ON.equals(signParams.get(ParamConstants.PARAM_CODE_SIGN))) { - CodeSigning codeSigning = new CodeSigning(signerConfig); - long offset = binFileLen + (long) HwBlockHead.getBlockLen() * blockNum; - byte[] codesignData = codeSigning.getCodeSignBlock(new File(inputFile), offset, "bin"); - return new SignBlockData(codesignData, CODESIGN_BLOCK_TYPE); - } - return null; + private static boolean checkBinAndProfileLengthIsValid(long binFileLen, long profileDataLen) { + return (binFileLen != -1) && (profileDataLen != -1) && (!isLongOverflowShort(profileDataLen)); } - private static SignBlockData generateSignByte() { - return new SignBlockData((byte[]) null, SignatureBlockTypes.SIGNATURE_BLOCK); - } - - - private static boolean writeSignHeadDataToOutputFile(String inputFile, String outputFile, int blockNum) { + private static boolean writeSignHeadDataToOutputFile(String inputFile, String outputFile) { long size = FileUtils.getFileLen(outputFile) - FileUtils.getFileLen(inputFile) + HwSignHead.SIGN_HEAD_LEN; if (isLongOverflowInteger(size)) { LOGGER.error("File size is Overflow integer range."); return false; } HwSignHead signHeadData = new HwSignHead(); - byte[] signHeadByte = signHeadData.getSignHead((int) size, blockNum); + byte[] signHeadByte = signHeadData.getSignHead((int) size, HwSignHead.NUM_OF_BLOCK); if (signHeadByte == null) { LOGGER.error("Failed to get sign head data."); return false; diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/sign/SignElf.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/sign/SignElf.java index b5a88b56..e517f862 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/sign/SignElf.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/sign/SignElf.java @@ -85,15 +85,6 @@ public class SignElf { LOGGER.info("The block head data made success."); /* 2. Make sign data, and write to output file */ - String signAlg = signParams.get(ParamConstants.PARAM_BASIC_SIGANTURE_ALG); - if (!writeSignDataToOutputFile(signerConfig, outputFile, signAlg)) { - LOGGER.error("The sign data made failed."); - ParamProcessUtil.delDir(new File(outputFile)); - return false; - } - LOGGER.info("The data signed success."); - - /* 3. Make sign data, and write to output file */ if (!writeSignHeadDataToOutputFile(inputFile, outputFile, blockNum)) { LOGGER.error("The sign head data made failed."); ParamProcessUtil.delDir(new File(outputFile)); -- Gitee