1 /* 2 * Copyright (c) 2023-2023 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16 package com.ohos.hapsigntool.codesigning.utils; 17 18 import com.google.gson.JsonArray; 19 import com.google.gson.JsonElement; 20 import com.google.gson.JsonObject; 21 import com.google.gson.JsonParseException; 22 import com.google.gson.JsonParser; 23 import com.google.gson.JsonPrimitive; 24 import com.google.gson.JsonSyntaxException; 25 import com.google.gson.stream.JsonReader; 26 import com.ohos.hapsigntool.codesigning.exception.CodeSignErrMsg; 27 import com.ohos.hapsigntool.entity.Pair; 28 import com.ohos.hapsigntool.error.ProfileException; 29 import com.ohos.hapsigntool.utils.LogUtils; 30 31 import java.io.BufferedReader; 32 import java.io.File; 33 import java.io.IOException; 34 import java.io.InputStream; 35 import java.io.InputStreamReader; 36 import java.nio.charset.StandardCharsets; 37 import java.util.ArrayList; 38 import java.util.Arrays; 39 import java.util.HashMap; 40 import java.util.List; 41 import java.util.Map; 42 import java.util.jar.JarEntry; 43 import java.util.jar.JarFile; 44 45 /** 46 * utility for check hap configs 47 * 48 * @since 2023/06/05 49 */ 50 public class HapUtils { 51 /** 52 * DEBUG_LIB_ID 53 */ 54 public static final String HAP_DEBUG_OWNER_ID = "DEBUG_LIB_ID"; 55 56 /** 57 * SHARED_LIB_ID 58 */ 59 public static final String HAP_SHARED_OWNER_ID = "SHARED_LIB_ID"; 60 61 private static final LogUtils LOGGER = new LogUtils(HapUtils.class); 62 63 private static final String COMPRESS_NATIVE_LIBS_OPTION = "compressNativeLibs"; 64 65 private static final List<String> HAP_CONFIG_FILES = new ArrayList<>(); 66 67 private static final String HAP_FA_CONFIG_JSON_FILE = "config.json"; 68 69 private static final String HAP_STAGE_MODULE_JSON_FILE = "module.json"; 70 71 private static final int MAX_APP_ID_LEN = 32; // max app-identifier in profile 72 73 static { 74 HAP_CONFIG_FILES.add(HAP_FA_CONFIG_JSON_FILE); 75 HAP_CONFIG_FILES.add(HAP_STAGE_MODULE_JSON_FILE); 76 } 77 HapUtils()78 private HapUtils() { 79 } 80 81 /** 82 * get app-id from profile 83 * 84 * @param profileContent the content of profile 85 * @return string value of app-id 86 * @throws ProfileException profile is invalid 87 */ getAppIdentifier(String profileContent)88 public static String getAppIdentifier(String profileContent) throws ProfileException { 89 Pair<String, String> resultPair = parseAppIdentifier(profileContent); 90 String ownerID = resultPair.getFirst(); 91 String profileType = resultPair.getSecond(); 92 if ("debug".equals(profileType)) { 93 return HAP_DEBUG_OWNER_ID; 94 } else if ("release".equals(profileType)) { 95 return ownerID; 96 } else { 97 throw new ProfileException(CodeSignErrMsg.PROFILE_TYPE_UNSUPPORTED_ERROR.toString()); 98 } 99 } 100 101 /** 102 * parse app-id and profileType from profile 103 * 104 * @param profileContent the content of profile 105 * @return Pair value of app-id and profileType 106 * @throws ProfileException profile is invalid 107 */ parseAppIdentifier(String profileContent)108 public static Pair<String, String> parseAppIdentifier(String profileContent) throws ProfileException { 109 String ownerID = null; 110 String profileType = null; 111 try { 112 JsonElement parser = JsonParser.parseString(profileContent); 113 JsonObject profileJson = parser.getAsJsonObject(); 114 String profileTypeKey = "type"; 115 if (!profileJson.has(profileTypeKey)) { 116 throw new ProfileException(CodeSignErrMsg.PROFILE_TYPE_NOT_EXISTED_ERROR.toString()); 117 } 118 119 profileType = profileJson.get(profileTypeKey).getAsString(); 120 if (profileType == null || profileType.isEmpty()) { 121 throw new ProfileException(CodeSignErrMsg.PROFILE_TYPE_NOT_EXISTED_ERROR.toString()); 122 } 123 124 String appIdentifier = "app-identifier"; 125 String buildInfoMember = "bundle-info"; 126 JsonObject buildInfoObject = profileJson.getAsJsonObject(buildInfoMember); 127 if (buildInfoObject == null) { 128 throw new ProfileException(CodeSignErrMsg.PROFILE_BUNDLE_INFO_NOT_EXISTED_ERROR.toString()); 129 } 130 if (buildInfoObject.has(appIdentifier)) { 131 JsonElement ownerIDElement = buildInfoObject.get(appIdentifier); 132 if (!ownerIDElement.getAsJsonPrimitive().isString()) { 133 throw new ProfileException(CodeSignErrMsg.PROFILE_APPID_VALUE_TYPE_ERROR.toString()); 134 } 135 ownerID = ownerIDElement.getAsString(); 136 if (ownerID.isEmpty() || ownerID.length() > MAX_APP_ID_LEN) { 137 throw new ProfileException(CodeSignErrMsg.PROFILE_APPID_VALUE_LENGTH_ERROR.toString()); 138 } 139 } 140 } catch (JsonSyntaxException | UnsupportedOperationException e) { 141 throw new ProfileException(CodeSignErrMsg.PROFILE_JSON_PARSE_ERROR.toString(), e); 142 } 143 LOGGER.info("profile type is: {}", profileType); 144 return Pair.create(ownerID, profileType); 145 } 146 147 /** 148 * get hnp app-id from profile when type is public 149 * 150 * @param profileContent the content of profile 151 * @return ownerid 152 */ getPublicHnpOwnerId(String profileContent)153 public static String getPublicHnpOwnerId(String profileContent) { 154 // property type 155 String publicOwnerID = ""; 156 JsonElement parser = JsonParser.parseString(profileContent); 157 JsonObject profileJson = parser.getAsJsonObject(); 158 String profileTypeKey = "type"; 159 JsonPrimitive profileType = profileJson.getAsJsonPrimitive(profileTypeKey); 160 if (profileType != null) { 161 if ("debug".equals(profileType.getAsString())) { 162 publicOwnerID = HAP_DEBUG_OWNER_ID; 163 } else if ("release".equals(profileType.getAsString())) { 164 publicOwnerID = HAP_SHARED_OWNER_ID; 165 } 166 } 167 return publicOwnerID; 168 } 169 170 /** 171 * get hnp path behind "hnp/abi/" 172 * 173 * @param path filepath 174 * @return hnp path behind "hnp/abi/" 175 */ parseHnpPath(String path)176 public static String parseHnpPath(String path) { 177 if (path == null || path.isEmpty()) { 178 return ""; 179 } 180 String[] strings = path.split("/"); 181 if (strings.length < 3) { 182 return ""; 183 } 184 // get hnp path behind "hnp/abi/" 185 strings = Arrays.copyOfRange(strings, 2, strings.length); 186 return String.join("/", strings); 187 } 188 189 /** 190 * get map of hnp name and type from module.json 191 * 192 * @param inputJar hap file 193 * @return packageName-type map 194 * @throws IOException when IO error occurred 195 * @throws ProfileException profile is invalid 196 */ getHnpsFromJson(JarFile inputJar)197 public static Map<String, String> getHnpsFromJson(JarFile inputJar) throws IOException, ProfileException { 198 // get module.json 199 Map<String, String> hnpNameMap = new HashMap<>(); 200 JarEntry moduleEntry = inputJar.getJarEntry(HAP_STAGE_MODULE_JSON_FILE); 201 if (moduleEntry == null) { 202 return hnpNameMap; 203 } 204 try (JsonReader reader = new JsonReader( 205 new InputStreamReader(inputJar.getInputStream(moduleEntry), StandardCharsets.UTF_8))) { 206 JsonElement jsonElement = JsonParser.parseReader(reader); 207 JsonObject jsonObject = jsonElement.getAsJsonObject(); 208 JsonObject moduleObject = jsonObject.getAsJsonObject("module"); 209 JsonArray hnpPackageArr = moduleObject.getAsJsonArray("hnpPackages"); 210 if (hnpPackageArr == null || hnpPackageArr.isEmpty()) { 211 LOGGER.debug("module.json has no hnpPackages key or hnpPackages value is empty"); 212 return hnpNameMap; 213 } 214 hnpPackageArr.iterator().forEachRemaining((element) -> { 215 JsonObject hnpPackage = element.getAsJsonObject(); 216 JsonPrimitive hnpName = hnpPackage.getAsJsonPrimitive("package"); 217 if (hnpName == null || hnpName.getAsString().isEmpty()) { 218 return; 219 } 220 hnpNameMap.put(hnpName.getAsString(), "private"); 221 JsonPrimitive type = hnpPackage.getAsJsonPrimitive("type"); 222 if (type != null && !type.getAsString().isEmpty()) { 223 hnpNameMap.put(hnpName.getAsString(), type.getAsString()); 224 } 225 }); 226 } catch (JsonParseException e) { 227 throw new ProfileException(CodeSignErrMsg.MODULE_JSON_PARSE_ERROR.toString(), e); 228 } 229 return hnpNameMap; 230 } 231 232 /** 233 * parse pluginDistributionIDs from profile 234 * 235 * @param profileContent the content of profile 236 * @return value of pluginDistributionIDs 237 * @throws ProfileException profile is invalid 238 */ parsePluginId(String profileContent)239 public static String parsePluginId(String profileContent) throws ProfileException { 240 String pluginID = null; 241 String pluginIDKey = "pluginDistributionIDs"; 242 String capabilitiesKey = "app-services-capabilities"; 243 String permissionKey = "ohos.permission.kernel.SUPPORT_PLUGIN"; 244 try { 245 JsonElement parser = JsonParser.parseString(profileContent); 246 if (parser == null || parser.isJsonNull()) { 247 throw new ProfileException(CodeSignErrMsg.PROFILE_JSON_PARSE_ERROR.toString()); 248 } 249 JsonObject profileJson = parser.getAsJsonObject(); 250 JsonObject capabilitiesObject = profileJson.getAsJsonObject(capabilitiesKey); 251 if (capabilitiesObject == null || !capabilitiesObject.isJsonObject() || !capabilitiesObject.has( 252 permissionKey)) { 253 throw new ProfileException(CodeSignErrMsg.PROFILE_PLUGIN_ID_NOT_EXISTED_ERROR.toString()); 254 } 255 JsonObject permissionObject = capabilitiesObject.getAsJsonObject(permissionKey); 256 if (permissionObject == null || !permissionObject.isJsonObject() || !permissionObject.has(pluginIDKey)) { 257 throw new ProfileException(CodeSignErrMsg.PROFILE_PLUGIN_ID_NOT_EXISTED_ERROR.toString()); 258 } 259 JsonElement permissionElement = permissionObject.get(pluginIDKey); 260 if (permissionElement == null || !permissionElement.getAsJsonPrimitive().isString()) { 261 throw new ProfileException(CodeSignErrMsg.PROFILE_PLUGIN_ID_VALUE_TYPE_ERROR.toString()); 262 } 263 pluginID = permissionElement.getAsString(); 264 } catch (JsonSyntaxException | UnsupportedOperationException e) { 265 throw new ProfileException(CodeSignErrMsg.PROFILE_JSON_PARSE_ERROR.toString(), e); 266 } 267 if (pluginID == null || pluginID.isEmpty()) { 268 throw new ProfileException(CodeSignErrMsg.PROFILE_PLUGIN_ID_VALUE_LENGTH_ERROR.toString()); 269 } 270 return pluginID; 271 } 272 273 /** 274 * get bundle type from module.json 275 * 276 * @param moduleContent module Content 277 * @return bundle type value 278 * @throws ProfileException profile is invalid 279 */ getBundleTypeFromJson(String moduleContent)280 public static String getBundleTypeFromJson(String moduleContent) throws ProfileException { 281 String bundleType = ""; 282 if (moduleContent == null || moduleContent.isEmpty()) { 283 return bundleType; 284 } 285 try { 286 JsonElement jsonElement = JsonParser.parseString(moduleContent); 287 if (jsonElement == null || jsonElement.isJsonNull()) { 288 throw new ProfileException(CodeSignErrMsg.MODULE_JSON_PARSE_ERROR.toString()); 289 } 290 JsonObject jsonObject = jsonElement.getAsJsonObject(); 291 JsonObject appObject = jsonObject.getAsJsonObject("app"); 292 if (appObject == null || !appObject.isJsonObject() || !appObject.has("bundleType")) { 293 return bundleType; 294 } 295 JsonPrimitive type = appObject.getAsJsonPrimitive("bundleType"); 296 if (type != null && !type.getAsString().isEmpty()) { 297 bundleType = type.getAsString(); 298 } 299 } catch (JsonSyntaxException | UnsupportedOperationException e) { 300 throw new ProfileException(CodeSignErrMsg.MODULE_JSON_PARSE_ERROR.toString(), e); 301 } 302 return bundleType; 303 } 304 305 /** 306 * get module.json content from input file 307 * @param input file 308 * @return module.json content 309 * @throws IOException when IO error occurred 310 */ getModuleContent(File input)311 public static String getModuleContent(File input) throws IOException { 312 try (JarFile inputJar = new JarFile(input, false)) { 313 JarEntry moduleEntry = inputJar.getJarEntry(HAP_STAGE_MODULE_JSON_FILE); 314 if (moduleEntry == null) { 315 return null; 316 } 317 try (InputStream inputStream = inputJar.getInputStream(moduleEntry); 318 BufferedReader reader = new BufferedReader( 319 new InputStreamReader(inputStream, StandardCharsets.UTF_8))) { 320 StringBuilder sb = new StringBuilder(); 321 String line; 322 while ((line = reader.readLine()) != null) { 323 sb.append(line); 324 } 325 return sb.toString(); 326 } 327 } 328 } 329 330 } 331