• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.phone.utils;
18 
19 import android.annotation.TestApi;
20 import android.content.Context;
21 import android.content.pm.PackageInfo;
22 import android.content.pm.PackageManager;
23 import android.content.pm.Signature;
24 import android.os.Binder;
25 import android.telephony.Rlog;
26 import android.text.TextUtils;
27 
28 import com.android.internal.telephony.flags.Flags;
29 import com.android.internal.telephony.uicc.IccUtils;
30 
31 import org.json.JSONArray;
32 import org.json.JSONException;
33 import org.json.JSONObject;
34 
35 import java.io.IOException;
36 import java.io.InputStream;
37 import java.security.MessageDigest;
38 import java.security.NoSuchAlgorithmException;
39 import java.util.ArrayList;
40 import java.util.Collections;
41 import java.util.HashSet;
42 import java.util.List;
43 import java.util.Set;
44 
45 
46 public class CarrierAllowListInfo {
47     private static final String LOG_TAG = "CarrierAllowListInfo";
48     private JSONObject mDataJSON;
49     private static final String JSON_CHARSET = "UTF-8";
50     private static final String MESSAGE_DIGEST_256_ALGORITHM = "SHA-256";
51     private static final String CALLER_SHA256_ID = "callerSHA256Ids";
52     private static final String CALLER_CARRIER_ID = "carrierIds";
53     public static final int INVALID_CARRIER_ID = -1;
54 
55     private static final String CARRIER_RESTRICTION_OPERATOR_REGISTERED_FILE =
56             "CarrierRestrictionOperatorDetails.json";
57 
58     private static CarrierAllowListInfo mInstance = null;
59     private Context mContext;
60 
CarrierAllowListInfo(Context context)61     private CarrierAllowListInfo(Context context) {
62         mContext = context;
63         loadJsonFile(context);
64     }
65 
loadInstance(Context context)66     public static CarrierAllowListInfo loadInstance(Context context) {
67         if (mInstance == null) {
68             mInstance = new CarrierAllowListInfo(context);
69         }
70         return mInstance;
71     }
72 
validateCallerAndGetCarrierIds(String packageName)73     public Set<Integer> validateCallerAndGetCarrierIds(String packageName) {
74         CarrierInfo carrierInfo = parseJsonForCallerInfo(packageName);
75         boolean isValid = (carrierInfo != null) && validateCallerSignature(mContext, packageName,
76                 carrierInfo.getSHAIdList());
77         return (isValid) ? carrierInfo.getCallerCarrierIdList() : Collections.singleton(
78                 INVALID_CARRIER_ID);
79     }
80 
loadJsonFile(Context context)81     private void loadJsonFile(Context context) {
82         try {
83             String jsonString = getJsonFromAssets(context,
84                     CARRIER_RESTRICTION_OPERATOR_REGISTERED_FILE, JSON_CHARSET);
85             if (!TextUtils.isEmpty(jsonString)) {
86                 mDataJSON = new JSONObject(jsonString);
87             }
88         } catch (Exception ex) {
89             Rlog.e(LOG_TAG, "CarrierAllowListInfo: JSON file reading exception = " + ex);
90         }
91     }
92 
93     /**
94      * Parse the JSON object to fetch the given caller's SHA-Ids and carrierId.
95      */
parseJsonForCallerInfo(String callerPackage)96     private CarrierInfo parseJsonForCallerInfo(String callerPackage) {
97         try {
98             if (mDataJSON != null && callerPackage != null) {
99                 JSONObject callerJSON = mDataJSON.getJSONObject(callerPackage.trim());
100                 JSONArray callerJSONArray = callerJSON.getJSONArray(CALLER_SHA256_ID);
101                 JSONArray carrierIdArray = callerJSON.getJSONArray(CALLER_CARRIER_ID);
102 
103                 Set<Integer> carrierIds = new HashSet<>();
104                 for (int index = 0; index < carrierIdArray.length(); index++) {
105                     carrierIds.add(carrierIdArray.getInt(index));
106                 }
107 
108                 List<String> appSignatures = new ArrayList<>();
109                 for (int index = 0; index < callerJSONArray.length(); index++) {
110                     appSignatures.add((String) callerJSONArray.get(index));
111                 }
112                 return new CarrierInfo(carrierIds, appSignatures);
113             }
114         } catch (JSONException ex) {
115             Rlog.e(LOG_TAG, "getCallerSignatureInfo: JSONException = " + ex);
116         }
117         return null;
118     }
119 
120     /**
121      * Read the Json file from the assert folder.
122      *
123      * @param context  context
124      * @param fileName JSON file name in assets folder
125      * @param charset  JSON file data format
126      * @return JSON file content in string format or null in case of IOException
127      */
getJsonFromAssets(Context context, String fileName, String charset)128     private static String getJsonFromAssets(Context context, String fileName, String charset) {
129         String jsonStr;
130         try {
131             InputStream ipStream = context.getAssets().open(fileName);
132             int bufSize = ipStream.available();
133             byte[] fileBuffer = new byte[bufSize];
134             ipStream.read(fileBuffer);
135             ipStream.close();
136             jsonStr = new String(fileBuffer, charset);
137         } catch (IOException ex) {
138             Rlog.e(LOG_TAG, "getJsonFromAssets: Exception = " + ex);
139             return null;
140         }
141         return jsonStr;
142     }
143 
144     /**
145      * API fetches all the related signatures of the given package from the packageManager
146      * and validate all the signatures using SHA-256.
147      *
148      * @param context             context
149      * @param packageName         package name of the caller to validate the signatures.
150      * @param allowListSignatures list of signatures to be validated.
151      * @return {@code true} if all the signatures are available with package manager.
152      * {@code false} if any one of the signatures won't match with package manager.
153      */
validateCallerSignature(Context context, String packageName, List<String> allowListSignatures)154     public static boolean validateCallerSignature(Context context, String packageName,
155             List<String> allowListSignatures) {
156         if (TextUtils.isEmpty(packageName) || allowListSignatures.size() == 0) {
157             // package name is mandatory
158             return false;
159         }
160         PackageManager packageManager = context.getPackageManager();
161         if (Flags.hsumPackageManager()) {
162             packageManager = context.createContextAsUser(Binder.getCallingUserHandle(), 0)
163                     .getPackageManager();
164         }
165         try {
166             MessageDigest sha256MDigest = MessageDigest.getInstance(MESSAGE_DIGEST_256_ALGORITHM);
167             final PackageInfo packageInfo = packageManager.getPackageInfo(packageName,
168                     PackageManager.GET_SIGNATURES);
169             for (Signature signature : packageInfo.signatures) {
170                 final byte[] signatureSha256 = sha256MDigest.digest(signature.toByteArray());
171                 final String hexSignatureSha256 = IccUtils.bytesToHexString(signatureSha256);
172                 if (!allowListSignatures.contains(hexSignatureSha256)) {
173                     return false;
174                 }
175             }
176             return true;
177         } catch (NoSuchAlgorithmException | PackageManager.NameNotFoundException ex) {
178             Rlog.e(LOG_TAG, "validateCallerSignature: Exception = " + ex);
179             return false;
180         }
181     }
182 
updateJsonForTest(String callerInfo)183     public int updateJsonForTest(String callerInfo) {
184         try {
185             if (callerInfo == null) {
186                 // reset the Json content after testing
187                 loadJsonFile(mContext);
188             } else {
189                 mDataJSON = new JSONObject(callerInfo);
190             }
191             return 0;
192         } catch (JSONException ex) {
193             Rlog.e(LOG_TAG, "updateJsonForTest: Exception = " + ex);
194         }
195         return -1;
196     }
197 
198     private static class CarrierInfo {
199         final private Set<Integer> mCallerCarrierIdList;
200         final private List<String> mSHAIdList;
201 
CarrierInfo(Set<Integer> carrierIds, List<String> SHAIds)202         public CarrierInfo(Set<Integer> carrierIds, List<String> SHAIds) {
203             mCallerCarrierIdList = carrierIds;
204             mSHAIdList = SHAIds;
205         }
206 
getCallerCarrierIdList()207         public Set<Integer> getCallerCarrierIdList() {
208             return mCallerCarrierIdList;
209         }
210 
getSHAIdList()211         public List<String> getSHAIdList() {
212             return mSHAIdList;
213         }
214     }
215 
216     @TestApi
getShaIdList(String srcPkg, int carrierId)217     public List<String> getShaIdList(String srcPkg, int carrierId) {
218         CarrierInfo carrierInfo = parseJsonForCallerInfo(srcPkg);
219         if (carrierInfo != null && carrierInfo.getCallerCarrierIdList().contains(carrierId)) {
220             return carrierInfo.getSHAIdList();
221         }
222         Rlog.e(LOG_TAG, "getShaIdList: carrierId or shaIdList is empty");
223         return Collections.EMPTY_LIST;
224     }
225 }
226