• 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.entity.Pair;
27 import com.ohos.hapsigntool.error.ProfileException;
28 import org.apache.logging.log4j.LogManager;
29 import org.apache.logging.log4j.Logger;
30 
31 import java.io.File;
32 import java.io.IOException;
33 import java.io.InputStream;
34 import java.io.InputStreamReader;
35 import java.nio.charset.StandardCharsets;
36 import java.util.ArrayList;
37 import java.util.Arrays;
38 import java.util.HashMap;
39 import java.util.LinkedList;
40 import java.util.List;
41 import java.util.Map;
42 import java.util.Queue;
43 import java.util.jar.JarEntry;
44 import java.util.jar.JarFile;
45 
46 /**
47  * utility for check hap configs
48  *
49  * @since 2023/06/05
50  */
51 public class HapUtils {
52     /**
53      * DEBUG_LIB_ID
54      */
55     public static final String HAP_DEBUG_OWNER_ID = "DEBUG_LIB_ID";
56 
57     /**
58      * SHARED_LIB_ID
59      */
60     public static final String HAP_SHARED_OWNER_ID = "SHARED_LIB_ID";
61 
62     private static final Logger LOGGER = LogManager.getLogger(HapUtils.class);
63 
64     private static final String COMPRESS_NATIVE_LIBS_OPTION = "compressNativeLibs";
65 
66     private static final List<String> HAP_CONFIG_FILES = new ArrayList<>();
67 
68     private static final String HAP_FA_CONFIG_JSON_FILE = "config.json";
69 
70     private static final String HAP_STAGE_MODULE_JSON_FILE = "module.json";
71 
72     private static final int MAX_APP_ID_LEN = 32; // max app-identifier in profile
73 
74     static {
75         HAP_CONFIG_FILES.add(HAP_FA_CONFIG_JSON_FILE);
76         HAP_CONFIG_FILES.add(HAP_STAGE_MODULE_JSON_FILE);
77     }
78 
HapUtils()79     private HapUtils() {
80     }
81 
82     /**
83      * Check configuration in hap to find out whether the native libs are compressed
84      *
85      * @param hapFile the given hap
86      * @return boolean value of parsing result
87      * @throws IOException io error
88      */
checkCompressNativeLibs(File hapFile)89     public static boolean checkCompressNativeLibs(File hapFile) throws IOException {
90         try (JarFile inputJar = new JarFile(hapFile, false)) {
91             for (String configFile : HAP_CONFIG_FILES) {
92                 JarEntry entry = inputJar.getJarEntry(configFile);
93                 if (entry == null) {
94                     continue;
95                 }
96                 try (InputStream data = inputJar.getInputStream(entry)) {
97                     String jsonString = new String(InputStreamUtils.toByteArray(data, (int) entry.getSize()),
98                         StandardCharsets.UTF_8);
99                     return checkCompressNativeLibs(jsonString);
100                 }
101             }
102         }
103         return true;
104     }
105 
106     /**
107      * Check whether the native libs are compressed by parsing config json
108      *
109      * @param jsonString the config json string
110      * @return boolean value of parsing result
111      */
checkCompressNativeLibs(String jsonString)112     public static boolean checkCompressNativeLibs(String jsonString) {
113         JsonObject jsonObject = JsonParser.parseString(jsonString).getAsJsonObject();
114         Queue<JsonObject> queue = new LinkedList<>();
115         queue.offer(jsonObject);
116         while (queue.size() > 0) {
117             JsonObject curJsonObject = queue.poll();
118             JsonElement jsonElement = curJsonObject.get(COMPRESS_NATIVE_LIBS_OPTION);
119             if (jsonElement != null) {
120                 return jsonElement.getAsBoolean();
121             }
122             for (Map.Entry<String, JsonElement> entry : curJsonObject.entrySet()) {
123                 if (entry.getValue().isJsonObject()) {
124                     queue.offer(entry.getValue().getAsJsonObject());
125                 }
126             }
127         }
128         // default to compress native libs
129         return true;
130     }
131 
132     /**
133      * get app-id from profile
134      *
135      * @param profileContent the content of profile
136      * @return string value of app-id
137      * @throws ProfileException profile is invalid
138      */
getAppIdentifier(String profileContent)139     public static String getAppIdentifier(String profileContent) throws ProfileException {
140         Pair<String, String> resultPair = parseAppIdentifier(profileContent);
141         String ownerID = resultPair.getFirst();
142         String profileType = resultPair.getSecond();
143         if ("debug".equals(profileType)) {
144             return HAP_DEBUG_OWNER_ID;
145         } else if ("release".equals(profileType)) {
146             return ownerID;
147         } else {
148             throw new ProfileException("unsupported profile type");
149         }
150     }
151 
152     /**
153      * parse app-id and profileType from profile
154      *
155      * @param profileContent the content of profile
156      * @return Pair value of app-id and profileType
157      * @throws ProfileException profile is invalid
158      */
parseAppIdentifier(String profileContent)159     public static Pair<String, String> parseAppIdentifier(String profileContent) throws ProfileException {
160         String ownerID = null;
161         String profileType = null;
162         try {
163             JsonElement parser = JsonParser.parseString(profileContent);
164             JsonObject profileJson = parser.getAsJsonObject();
165             String profileTypeKey = "type";
166             if (!profileJson.has(profileTypeKey)) {
167                 throw new ProfileException("profile has no type key");
168             }
169 
170             profileType = profileJson.get(profileTypeKey).getAsString();
171             if (profileType == null || profileType.length() == 0) {
172                 throw new ProfileException("Get profile type error");
173             }
174 
175             String appIdentifier = "app-identifier";
176             String buildInfoMember = "bundle-info";
177             JsonObject buildInfoObject = profileJson.getAsJsonObject(buildInfoMember);
178             if (buildInfoObject == null) {
179                 throw new ProfileException("can not find bundle-info");
180             }
181             if (buildInfoObject.has(appIdentifier)) {
182                 JsonElement ownerIDElement = buildInfoObject.get(appIdentifier);
183                 if (!ownerIDElement.getAsJsonPrimitive().isString()) {
184                     throw new ProfileException("value of app-identifier is not string");
185                 }
186                 ownerID = ownerIDElement.getAsString();
187                 if (ownerID.isEmpty() || ownerID.length() > MAX_APP_ID_LEN) {
188                     throw new ProfileException("app-id length in profile is invalid");
189                 }
190 
191             }
192         } catch (JsonSyntaxException | UnsupportedOperationException e) {
193             LOGGER.error(e.getMessage());
194             throw new ProfileException("profile json is invalid");
195         }
196         LOGGER.info("profile type is: {}", profileType);
197         return Pair.create(ownerID, profileType);
198     }
199 
200     /**
201      * get hnp app-id from profile when type is public
202      *
203      * @param profileContent the content of profile
204      * @return ownerid
205      */
getPublicHnpOwnerId(String profileContent)206     public static String getPublicHnpOwnerId(String profileContent) {
207         // property type
208         String publicOwnerID = "";
209         JsonElement parser = JsonParser.parseString(profileContent);
210         JsonObject profileJson = parser.getAsJsonObject();
211         String profileTypeKey = "type";
212         JsonPrimitive profileType = profileJson.getAsJsonPrimitive(profileTypeKey);
213         if (profileType != null) {
214             if ("debug".equals(profileType.getAsString())) {
215                 publicOwnerID = HAP_DEBUG_OWNER_ID;
216             } else if ("release".equals(profileType.getAsString())) {
217                 publicOwnerID = HAP_SHARED_OWNER_ID;
218             }
219         }
220         return publicOwnerID;
221     }
222 
223     /**
224      * get hnp path behind "hnp/abi/"
225      *
226      * @param path filepath
227      * @return hnp path behind "hnp/abi/"
228      */
parseHnpPath(String path)229     public static String parseHnpPath(String path) {
230         if (path == null || path.isEmpty()) {
231             return "";
232         }
233         String[] strings = path.split("/");
234         if (strings.length < 3) {
235             return "";
236         }
237         // get hnp path behind "hnp/abi/"
238         strings = Arrays.copyOfRange(strings, 2, strings.length);
239         return String.join("/", strings);
240     }
241 
242     /**
243      * get map of hnp name and type from module.json
244      *
245      * @param inputJar hap file
246      * @return packageName-type map
247      * @throws IOException when IO error occurred
248      * @throws ProfileException profile is invalid
249      */
getHnpsFromJson(JarFile inputJar)250     public static Map<String, String> getHnpsFromJson(JarFile inputJar) throws IOException, ProfileException {
251         // get module.json
252         Map<String, String> hnpNameMap = new HashMap<>();
253         JarEntry moduleEntry = inputJar.getJarEntry("module.json");
254         if (moduleEntry == null) {
255             return hnpNameMap;
256         }
257         try (JsonReader reader = new JsonReader(
258             new InputStreamReader(inputJar.getInputStream(moduleEntry), StandardCharsets.UTF_8))) {
259             JsonElement jsonElement = JsonParser.parseReader(reader);
260             JsonObject jsonObject = jsonElement.getAsJsonObject();
261             JsonObject moduleObject = jsonObject.getAsJsonObject("module");
262             JsonArray hnpPackageArr = moduleObject.getAsJsonArray("hnpPackages");
263             if (hnpPackageArr == null || hnpPackageArr.isEmpty()) {
264                 LOGGER.debug("profile has no hnpPackages key or hnpPackages value is empty");
265                 return hnpNameMap;
266             }
267             hnpPackageArr.iterator().forEachRemaining((element) -> {
268                 JsonObject hnpPackage = element.getAsJsonObject();
269                 JsonPrimitive hnpName = hnpPackage.getAsJsonPrimitive("package");
270                 if (hnpName == null || hnpName.getAsString().isEmpty()) {
271                     return;
272                 }
273                 hnpNameMap.put(hnpName.getAsString(), "private");
274                 JsonPrimitive type = hnpPackage.getAsJsonPrimitive("type");
275                 if (type != null && !type.getAsString().isEmpty()) {
276                     hnpNameMap.put(hnpName.getAsString(), type.getAsString());
277                 }
278             });
279         } catch (JsonParseException e) {
280             LOGGER.error(e.getMessage());
281             throw new ProfileException("profile json is invalid");
282         }
283         return hnpNameMap;
284     }
285 
286 }
287