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