diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/exception/CodeSignErrMsg.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/exception/CodeSignErrMsg.java index aa1ac5aa6985db916040d1aa31715498a649cb01..efa4df0fde2163668c0f7dc01c3569b0845c7d07 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/exception/CodeSignErrMsg.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/codesigning/exception/CodeSignErrMsg.java @@ -99,6 +99,17 @@ public class CodeSignErrMsg { .addSolution("Add 'bundle-info' to the profile file") .build(); + /** + * PROFILE_PLUGIN_ID_NOT_EXISTED_ERROR + */ + public static final ErrorMsg PROFILE_PLUGIN_ID_NOT_EXISTED_ERROR = ErrorMsg.getCodeSignErrBuilder() + .addTypeCode("12") + .addErrCode("001") + .addDescription("Profile Content Error") + .addCause("'pluginDistributionIDs' not found in profile file") + .addSolution("Add 'pluginDistributionIDs' to the profile file") + .build(); + /** * PROFILE_APPID_VALUE_TYPE_ERROR */ @@ -110,6 +121,17 @@ public class CodeSignErrMsg { .addSolution("Value type of app-identifier should be string") .build(); + /** + * PROFILE_PLUGIN_ID_VALUE_TYPE_ERROR + */ + public static final ErrorMsg PROFILE_PLUGIN_ID_VALUE_TYPE_ERROR = ErrorMsg.getCodeSignErrBuilder() + .addTypeCode("12") + .addErrCode("001") + .addDescription("Profile Content Error") + .addCause("Value type of pluginDistributionIDs is not string") + .addSolution("Value type of pluginDistributionIDs should be string") + .build(); + /** * PROFILE_APPID_VALUE_LENGTH_ERROR */ @@ -121,6 +143,17 @@ public class CodeSignErrMsg { .addSolution("Modify to a valid app-identifier in profile file") .build(); + /** + * PROFILE_PLUGIN_ID_VALUE_LENGTH_ERROR + */ + public static final ErrorMsg PROFILE_PLUGIN_ID_VALUE_LENGTH_ERROR = ErrorMsg.getCodeSignErrBuilder() + .addTypeCode("12") + .addErrCode("001") + .addDescription("Profile Content Error") + .addCause("Value length of pluginDistributionIDs is invalid") + .addSolution("Modify to a valid pluginDistributionIDs in profile file") + .build(); + /** * PROFILE_JSON_PARSE_ERROR */ 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 index 9dcc97705e21e891887427943da04f0bd441d8d1..1f78029e5c65d3e90f1ac8bb42a10282ce637fbe 100644 --- 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 @@ -74,6 +74,11 @@ public class BcSignedDataGenerator implements SignedDataGenerator { */ public static final String SIGNER_OID = "1.3.6.1.4.1.2011.2.376.1.4.1"; + /** + * PLUGIN ID of the signer identity + */ + public static final String SIGNER_PLUGIN_ID = "1.3.6.1.4.1.2011.2.376.1.4.2"; + private static final LogUtils LOGGER = new LogUtils(BcSignedDataGenerator.class); private static final SignatureAlgorithmIdentifierFinder SIGN_ALG_ID_FINDER @@ -84,10 +89,16 @@ public class BcSignedDataGenerator implements SignedDataGenerator { private String ownerID; + private String pluginId; + public void setOwnerID(String ownerID) { this.ownerID = ownerID; } + public void setPluginId(String pluginId) { + this.pluginId = pluginId; + } + @Override public byte[] generateSignedData(byte[] content, SignerConfig signConfig) throws CodeSignException { if (content == null) { @@ -197,6 +208,11 @@ public class BcSignedDataGenerator implements SignedDataGenerator { new DERSet(new DERUTF8String(ownerID))); table.add(ownerIDAttr); } + if (pluginId != null) { + Attribute pluginIDAttr = new Attribute(new ASN1ObjectIdentifier(SIGNER_PLUGIN_ID), + new DERSet(new DERUTF8String(pluginId))); + table.add(pluginIDAttr); + } return new DERSet(table); } 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 1e1bf4a1adea85078394b49a3781129bcb3ce5d4..c7a512b3c04d726a1eecfc9a209fa32c9bcdd853 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 @@ -80,6 +80,11 @@ public class CodeSigning { */ public static final String HAP_SIGNATURE_ENTRY_NAME = "Hap"; + /** + * bundle type of plugin + */ + public static final String BUNDLE_TYPE_PLUGIN = "appPlugin"; + private static final LogUtils LOGGER = new LogUtils(CodeSigning.class); private final SignerConfig signConfig; @@ -88,6 +93,8 @@ public class CodeSigning { private PageInfoExtension pageInfoExtension; + private String pluginId; + /** * provide code sign functions to sign a hap * @@ -182,8 +189,11 @@ public class CodeSigning { FsVerityInfoSegment fsVerityInfoSegment = new FsVerityInfoSegment(FsVerityDescriptor.VERSION, FsVerityGenerator.getFsVerityHashAlgorithm(), FsVerityGenerator.getLog2BlockSize()); this.codeSignBlock.setFsVerityInfoSegment(fsVerityInfoSegment); - - LOGGER.debug("Sign hap."); + String moduleContent = HapUtils.getModuleContent(input); + String bundleType = HapUtils.getBundleTypeFromJson(moduleContent); + if (BUNDLE_TYPE_PLUGIN.equals(bundleType)) { + pluginId = HapUtils.parsePluginId(profileContent); + } String ownerID = HapUtils.getAppIdentifier(profileContent); try (FileInputStream inputStream = new FileInputStream(input)) { Pair hapSignInfoAndMerkleTreeBytesPair = signFile(inputStream, dataSize, true, @@ -486,11 +496,13 @@ public class CodeSigning { } BcSignedDataGenerator bcSignedDataGenerator = new BcSignedDataGenerator(); bcSignedDataGenerator.setOwnerID(ownerID); + bcSignedDataGenerator.setPluginId(pluginId); return bcSignedDataGenerator.generateSignedData(signedData, copiedConfig); } else { copiedConfig = signConfig.copy(); BcSignedDataGenerator bcSignedDataGenerator = new BcSignedDataGenerator(); bcSignedDataGenerator.setOwnerID(ownerID); + bcSignedDataGenerator.setPluginId(pluginId); return bcSignedDataGenerator.generateSignedData(signedData, copiedConfig); } } 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 index f3053573a883f4fa0689b5621c0b8a89b38c450d..691d102be7b732d1cc81c614175eb0cf2b4db4b6 100644 --- 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 @@ -28,7 +28,10 @@ import com.ohos.hapsigntool.entity.Pair; import com.ohos.hapsigntool.error.ProfileException; import com.ohos.hapsigntool.utils.LogUtils; +import java.io.BufferedReader; +import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; import java.util.ArrayList; @@ -194,7 +197,7 @@ public class HapUtils { public static Map getHnpsFromJson(JarFile inputJar) throws IOException, ProfileException { // get module.json Map hnpNameMap = new HashMap<>(); - JarEntry moduleEntry = inputJar.getJarEntry("module.json"); + JarEntry moduleEntry = inputJar.getJarEntry(HAP_STAGE_MODULE_JSON_FILE); if (moduleEntry == null) { return hnpNameMap; } @@ -226,4 +229,102 @@ public class HapUtils { return hnpNameMap; } + /** + * parse pluginDistributionIDs from profile + * + * @param profileContent the content of profile + * @return value of pluginDistributionIDs + * @throws ProfileException profile is invalid + */ + public static String parsePluginId(String profileContent) throws ProfileException { + String pluginID = null; + String pluginIDKey = "pluginDistributionIDs"; + String capabilitiesKey = "app-services-capabilities"; + String permissionKey = "ohos.permission.kernel.SUPPORT_PLUGIN"; + try { + JsonElement parser = JsonParser.parseString(profileContent); + if (parser == null || parser.isJsonNull()) { + throw new ProfileException(CodeSignErrMsg.PROFILE_JSON_PARSE_ERROR.toString()); + } + JsonObject profileJson = parser.getAsJsonObject(); + JsonObject capabilitiesObject = profileJson.getAsJsonObject(capabilitiesKey); + if (capabilitiesObject == null || !capabilitiesObject.isJsonObject() || !capabilitiesObject.has( + permissionKey)) { + throw new ProfileException(CodeSignErrMsg.PROFILE_PLUGIN_ID_NOT_EXISTED_ERROR.toString()); + } + JsonObject permissionObject = capabilitiesObject.getAsJsonObject(permissionKey); + if (permissionObject == null || !permissionObject.isJsonObject() || !permissionObject.has(pluginIDKey)) { + throw new ProfileException(CodeSignErrMsg.PROFILE_PLUGIN_ID_NOT_EXISTED_ERROR.toString()); + } + JsonElement permissionElement = permissionObject.get(pluginIDKey); + if (permissionElement == null || !permissionElement.getAsJsonPrimitive().isString()) { + throw new ProfileException(CodeSignErrMsg.PROFILE_PLUGIN_ID_VALUE_TYPE_ERROR.toString()); + } + pluginID = permissionElement.getAsString(); + } catch (JsonSyntaxException | UnsupportedOperationException e) { + throw new ProfileException(CodeSignErrMsg.PROFILE_JSON_PARSE_ERROR.toString(), e); + } + if (pluginID == null || pluginID.isEmpty()) { + throw new ProfileException(CodeSignErrMsg.PROFILE_PLUGIN_ID_VALUE_LENGTH_ERROR.toString()); + } + return pluginID; + } + + /** + * get bundle type from module.json + * + * @param moduleContent module Content + * @return bundle type value + * @throws ProfileException profile is invalid + */ + public static String getBundleTypeFromJson(String moduleContent) throws ProfileException { + String bundleType = ""; + if (moduleContent == null || moduleContent.isEmpty()) { + return bundleType; + } + try { + JsonElement jsonElement = JsonParser.parseString(moduleContent); + if (jsonElement == null || jsonElement.isJsonNull()) { + throw new ProfileException(CodeSignErrMsg.MODULE_JSON_PARSE_ERROR.toString()); + } + JsonObject jsonObject = jsonElement.getAsJsonObject(); + JsonObject appObject = jsonObject.getAsJsonObject("app"); + if (appObject == null || !appObject.isJsonObject() || !appObject.has("bundleType")) { + return bundleType; + } + JsonPrimitive type = appObject.getAsJsonPrimitive("bundleType"); + if (type != null && !type.getAsString().isEmpty()) { + bundleType = type.getAsString(); + } + } catch (JsonSyntaxException | UnsupportedOperationException e) { + throw new ProfileException(CodeSignErrMsg.MODULE_JSON_PARSE_ERROR.toString(), e); + } + return bundleType; + } + + /** + * get module.json content from input file + * @param input file + * @return module.json content + * @throws IOException when IO error occurred + */ + public static String getModuleContent(File input) throws IOException { + try (JarFile inputJar = new JarFile(input, false)) { + JarEntry moduleEntry = inputJar.getJarEntry(HAP_STAGE_MODULE_JSON_FILE); + if (moduleEntry == null) { + return null; + } + try (InputStream inputStream = inputJar.getInputStream(moduleEntry); + BufferedReader reader = new BufferedReader( + new InputStreamReader(inputStream, StandardCharsets.UTF_8))) { + StringBuilder sb = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + sb.append(line); + } + return sb.toString(); + } + } + } + }