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.telephony.Rlog; 25 import android.text.TextUtils; 26 27 import com.android.internal.telephony.uicc.IccUtils; 28 29 import org.json.JSONArray; 30 import org.json.JSONException; 31 import org.json.JSONObject; 32 33 import java.io.IOException; 34 import java.io.InputStream; 35 import java.security.MessageDigest; 36 import java.security.NoSuchAlgorithmException; 37 import java.util.ArrayList; 38 import java.util.Collections; 39 import java.util.HashSet; 40 import java.util.List; 41 import java.util.Set; 42 43 44 public class CarrierAllowListInfo { 45 private static final String LOG_TAG = "CarrierAllowListInfo"; 46 private JSONObject mDataJSON; 47 private static final String JSON_CHARSET = "UTF-8"; 48 private static final String MESSAGE_DIGEST_256_ALGORITHM = "SHA-256"; 49 private static final String CALLER_SHA256_ID = "callerSHA256Ids"; 50 private static final String CALLER_CARRIER_ID = "carrierIds"; 51 public static final int INVALID_CARRIER_ID = -1; 52 53 private static final String CARRIER_RESTRICTION_OPERATOR_REGISTERED_FILE = 54 "CarrierRestrictionOperatorDetails.json"; 55 56 private static CarrierAllowListInfo mInstance = null; 57 private Context mContext; 58 CarrierAllowListInfo(Context context)59 private CarrierAllowListInfo(Context context) { 60 mContext = context; 61 loadJsonFile(context); 62 } 63 loadInstance(Context context)64 public static CarrierAllowListInfo loadInstance(Context context) { 65 if (mInstance == null) { 66 mInstance = new CarrierAllowListInfo(context); 67 } 68 return mInstance; 69 } 70 validateCallerAndGetCarrierIds(String packageName)71 public Set<Integer> validateCallerAndGetCarrierIds(String packageName) { 72 CarrierInfo carrierInfo = parseJsonForCallerInfo(packageName); 73 boolean isValid = (carrierInfo != null) && validateCallerSignature(mContext, packageName, 74 carrierInfo.getSHAIdList()); 75 return (isValid) ? carrierInfo.getCallerCarrierIdList() : Collections.singleton( 76 INVALID_CARRIER_ID); 77 } 78 loadJsonFile(Context context)79 private void loadJsonFile(Context context) { 80 try { 81 String jsonString = getJsonFromAssets(context, 82 CARRIER_RESTRICTION_OPERATOR_REGISTERED_FILE, JSON_CHARSET); 83 if (!TextUtils.isEmpty(jsonString)) { 84 mDataJSON = new JSONObject(jsonString); 85 } 86 } catch (Exception ex) { 87 Rlog.e(LOG_TAG, "CarrierAllowListInfo: JSON file reading exception = " + ex); 88 } 89 } 90 91 /** 92 * Parse the JSON object to fetch the given caller's SHA-Ids and carrierId. 93 */ parseJsonForCallerInfo(String callerPackage)94 private CarrierInfo parseJsonForCallerInfo(String callerPackage) { 95 try { 96 if (mDataJSON != null && callerPackage != null) { 97 JSONObject callerJSON = mDataJSON.getJSONObject(callerPackage.trim()); 98 JSONArray callerJSONArray = callerJSON.getJSONArray(CALLER_SHA256_ID); 99 JSONArray carrierIdArray = callerJSON.getJSONArray(CALLER_CARRIER_ID); 100 101 Set<Integer> carrierIds = new HashSet<>(); 102 for (int index = 0; index < carrierIdArray.length(); index++) { 103 carrierIds.add(carrierIdArray.getInt(index)); 104 } 105 106 List<String> appSignatures = new ArrayList<>(); 107 for (int index = 0; index < callerJSONArray.length(); index++) { 108 appSignatures.add((String) callerJSONArray.get(index)); 109 } 110 return new CarrierInfo(carrierIds, appSignatures); 111 } 112 } catch (JSONException ex) { 113 Rlog.e(LOG_TAG, "getCallerSignatureInfo: JSONException = " + ex); 114 } 115 return null; 116 } 117 118 /** 119 * Read the Json file from the assert folder. 120 * 121 * @param context context 122 * @param fileName JSON file name in assets folder 123 * @param charset JSON file data format 124 * @return JSON file content in string format or null in case of IOException 125 */ getJsonFromAssets(Context context, String fileName, String charset)126 private static String getJsonFromAssets(Context context, String fileName, String charset) { 127 String jsonStr; 128 try { 129 InputStream ipStream = context.getAssets().open(fileName); 130 int bufSize = ipStream.available(); 131 byte[] fileBuffer = new byte[bufSize]; 132 ipStream.read(fileBuffer); 133 ipStream.close(); 134 jsonStr = new String(fileBuffer, charset); 135 } catch (IOException ex) { 136 Rlog.e(LOG_TAG, "getJsonFromAssets: Exception = " + ex); 137 return null; 138 } 139 return jsonStr; 140 } 141 142 /** 143 * API fetches all the related signatures of the given package from the packageManager 144 * and validate all the signatures using SHA-256. 145 * 146 * @param context context 147 * @param packageName package name of the caller to validate the signatures. 148 * @param allowListSignatures list of signatures to be validated. 149 * @return {@code true} if all the signatures are available with package manager. 150 * {@code false} if any one of the signatures won't match with package manager. 151 */ validateCallerSignature(Context context, String packageName, List<String> allowListSignatures)152 public static boolean validateCallerSignature(Context context, String packageName, 153 List<String> allowListSignatures) { 154 if (TextUtils.isEmpty(packageName) || allowListSignatures.size() == 0) { 155 // package name is mandatory 156 return false; 157 } 158 final PackageManager packageManager = context.getPackageManager(); 159 try { 160 MessageDigest sha256MDigest = MessageDigest.getInstance(MESSAGE_DIGEST_256_ALGORITHM); 161 final PackageInfo packageInfo = packageManager.getPackageInfo(packageName, 162 PackageManager.GET_SIGNATURES); 163 for (Signature signature : packageInfo.signatures) { 164 final byte[] signatureSha256 = sha256MDigest.digest(signature.toByteArray()); 165 final String hexSignatureSha256 = IccUtils.bytesToHexString(signatureSha256); 166 if (!allowListSignatures.contains(hexSignatureSha256)) { 167 return false; 168 } 169 } 170 return true; 171 } catch (NoSuchAlgorithmException | PackageManager.NameNotFoundException ex) { 172 Rlog.e(LOG_TAG, "validateCallerSignature: Exception = " + ex); 173 return false; 174 } 175 } 176 updateJsonForTest(String callerInfo)177 public int updateJsonForTest(String callerInfo) { 178 try { 179 if (callerInfo == null) { 180 // reset the Json content after testing 181 loadJsonFile(mContext); 182 } else { 183 mDataJSON = new JSONObject(callerInfo); 184 } 185 return 0; 186 } catch (JSONException ex) { 187 Rlog.e(LOG_TAG, "updateJsonForTest: Exception = " + ex); 188 } 189 return -1; 190 } 191 192 private static class CarrierInfo { 193 final private Set<Integer> mCallerCarrierIdList; 194 final private List<String> mSHAIdList; 195 CarrierInfo(Set<Integer> carrierIds, List<String> SHAIds)196 public CarrierInfo(Set<Integer> carrierIds, List<String> SHAIds) { 197 mCallerCarrierIdList = carrierIds; 198 mSHAIdList = SHAIds; 199 } 200 getCallerCarrierIdList()201 public Set<Integer> getCallerCarrierIdList() { 202 return mCallerCarrierIdList; 203 } 204 getSHAIdList()205 public List<String> getSHAIdList() { 206 return mSHAIdList; 207 } 208 } 209 210 @TestApi getShaIdList(String srcPkg, int carrierId)211 public List<String> getShaIdList(String srcPkg, int carrierId) { 212 CarrierInfo carrierInfo = parseJsonForCallerInfo(srcPkg); 213 if (carrierInfo != null && carrierInfo.getCallerCarrierIdList().contains(carrierId)) { 214 return carrierInfo.getSHAIdList(); 215 } 216 Rlog.e(LOG_TAG, "getShaIdList: carrierId or shaIdList is empty"); 217 return Collections.EMPTY_LIST; 218 } 219 } 220