• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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