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.JsonElement; 19 import com.google.gson.JsonObject; 20 import com.google.gson.JsonParser; 21 import com.google.gson.JsonSyntaxException; 22 import com.ohos.hapsigntool.entity.Pair; 23 import com.ohos.hapsigntool.error.ProfileException; 24 import org.apache.logging.log4j.LogManager; 25 import org.apache.logging.log4j.Logger; 26 27 import java.io.File; 28 import java.io.IOException; 29 import java.io.InputStream; 30 import java.nio.charset.StandardCharsets; 31 import java.util.ArrayList; 32 import java.util.LinkedList; 33 import java.util.List; 34 import java.util.Map; 35 import java.util.Queue; 36 import java.util.jar.JarEntry; 37 import java.util.jar.JarFile; 38 39 /** 40 * utility for check hap configs 41 * 42 * @since 2023/06/05 43 */ 44 public class HapUtils { 45 private static final Logger LOGGER = LogManager.getLogger(HapUtils.class); 46 47 private static final String COMPRESS_NATIVE_LIBS_OPTION = "compressNativeLibs"; 48 49 private static final List<String> HAP_CONFIG_FILES = new ArrayList<>(); 50 51 private static final String HAP_FA_CONFIG_JSON_FILE = "config.json"; 52 53 private static final String HAP_STAGE_MODULE_JSON_FILE = "module.json"; 54 55 private static final String HAP_DEBUG_OWNER_ID = "DEBUG_LIB_ID"; 56 57 private static final int MAX_APP_ID_LEN = 32; // max app-identifier in profile 58 59 static { 60 HAP_CONFIG_FILES.add(HAP_FA_CONFIG_JSON_FILE); 61 HAP_CONFIG_FILES.add(HAP_STAGE_MODULE_JSON_FILE); 62 } 63 HapUtils()64 private HapUtils() { 65 } 66 67 /** 68 * Check configuration in hap to find out whether the native libs are compressed 69 * 70 * @param hapFile the given hap 71 * @return boolean value of parsing result 72 * @throws IOException io error 73 */ checkCompressNativeLibs(File hapFile)74 public static boolean checkCompressNativeLibs(File hapFile) throws IOException { 75 try (JarFile inputJar = new JarFile(hapFile, false)) { 76 for (String configFile : HAP_CONFIG_FILES) { 77 JarEntry entry = inputJar.getJarEntry(configFile); 78 if (entry == null) { 79 continue; 80 } 81 try (InputStream data = inputJar.getInputStream(entry)) { 82 String jsonString = new String(InputStreamUtils.toByteArray(data, (int) entry.getSize()), 83 StandardCharsets.UTF_8); 84 return checkCompressNativeLibs(jsonString); 85 } 86 } 87 } 88 return true; 89 } 90 91 /** 92 * Check whether the native libs are compressed by parsing config json 93 * 94 * @param jsonString the config json string 95 * @return boolean value of parsing result 96 */ checkCompressNativeLibs(String jsonString)97 public static boolean checkCompressNativeLibs(String jsonString) { 98 JsonObject jsonObject = JsonParser.parseString(jsonString).getAsJsonObject(); 99 Queue<JsonObject> queue = new LinkedList<>(); 100 queue.offer(jsonObject); 101 while (queue.size() > 0) { 102 JsonObject curJsonObject = queue.poll(); 103 JsonElement jsonElement = curJsonObject.get(COMPRESS_NATIVE_LIBS_OPTION); 104 if (jsonElement != null) { 105 return jsonElement.getAsBoolean(); 106 } 107 for (Map.Entry<String, JsonElement> entry : curJsonObject.entrySet()) { 108 if (entry.getValue().isJsonObject()) { 109 queue.offer(entry.getValue().getAsJsonObject()); 110 } 111 } 112 } 113 // default to compress native libs 114 return true; 115 } 116 117 /** 118 * get app-id from profile 119 * 120 * @param profileContent the content of profile 121 * @return string value of app-id 122 * @throws ProfileException profile is invalid 123 */ getAppIdentifier(String profileContent)124 public static String getAppIdentifier(String profileContent) throws ProfileException { 125 Pair<String, String> resultPair = parseAppIdentifier(profileContent); 126 String ownerID = resultPair.getFirst(); 127 String profileType = resultPair.getSecond(); 128 if ("debug".equals(profileType)) { 129 return HAP_DEBUG_OWNER_ID; 130 } else if ("release".equals(profileType)) { 131 return ownerID; 132 } else { 133 throw new ProfileException("unsupported profile type"); 134 } 135 } 136 137 /** 138 * parse app-id and profileType from profile 139 * 140 * @param profileContent the content of profile 141 * @return Pair value of app-id and profileType 142 * @throws ProfileException profile is invalid 143 */ parseAppIdentifier(String profileContent)144 public static Pair<String, String> parseAppIdentifier(String profileContent) throws ProfileException { 145 String ownerID = null; 146 String profileType = null; 147 try { 148 JsonElement parser = JsonParser.parseString(profileContent); 149 JsonObject profileJson = parser.getAsJsonObject(); 150 String profileTypeKey = "type"; 151 if (!profileJson.has(profileTypeKey)) { 152 throw new ProfileException("profile has no type key"); 153 } 154 155 profileType = profileJson.get(profileTypeKey).getAsString(); 156 if (profileType == null || profileType.length() == 0) { 157 throw new ProfileException("Get profile type error"); 158 } 159 160 String appIdentifier = "app-identifier"; 161 String buildInfoMember = "bundle-info"; 162 JsonObject buildInfoObject = profileJson.getAsJsonObject(buildInfoMember); 163 if (buildInfoObject == null) { 164 throw new ProfileException("can not find bundle-info"); 165 } 166 if (buildInfoObject.has(appIdentifier)) { 167 JsonElement ownerIDElement = buildInfoObject.get(appIdentifier); 168 if (!ownerIDElement.getAsJsonPrimitive().isString()) { 169 throw new ProfileException("value of app-identifier is not string"); 170 } 171 ownerID = ownerIDElement.getAsString(); 172 if (ownerID.isEmpty() || ownerID.length() > MAX_APP_ID_LEN) { 173 throw new ProfileException("app-id length in profile is invalid"); 174 } 175 176 } 177 } catch (JsonSyntaxException | UnsupportedOperationException e) { 178 LOGGER.error(e.getMessage()); 179 throw new ProfileException("profile json is invalid"); 180 } 181 return Pair.create(ownerID, profileType); 182 } 183 } 184