• 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.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