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