1 /* 2 * Copyright (C) 2021 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 android.telephony.cts.util; 18 19 import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity; 20 import static com.android.internal.util.FunctionalUtils.ThrowingRunnable; 21 import static com.android.internal.util.FunctionalUtils.ThrowingSupplier; 22 23 import android.content.BroadcastReceiver; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.content.IntentFilter; 27 import android.content.pm.PackageInfo; 28 import android.content.pm.PackageManager; 29 import android.os.PersistableBundle; 30 import android.telephony.CarrierConfigManager; 31 import android.telephony.SubscriptionManager; 32 import android.telephony.TelephonyManager; 33 import android.util.Log; 34 35 import com.android.internal.telephony.uicc.IccUtils; 36 37 import java.security.MessageDigest; 38 import java.util.concurrent.CountDownLatch; 39 import java.util.concurrent.TimeUnit; 40 41 /** 42 * Utility to execute a code block with carrier privileges. 43 * 44 * <p>The utility methods contained in this class will release carrier privileges once the specified 45 * task is completed. 46 * 47 * <p>Example: 48 * 49 * <pre> 50 * CarrierPrivilegeUtils.withCarrierPrivileges(c, subId, () -> telephonyManager.setFoo(bar)); 51 * </pre> 52 */ 53 public class CarrierPrivilegeUtils { 54 private static final String TAG = CarrierPrivilegeUtils.class.getSimpleName(); 55 56 private static class CarrierPrivilegeReceiver extends BroadcastReceiver 57 implements AutoCloseable { 58 59 private final CountDownLatch mLatch = new CountDownLatch(1); 60 private final Context mContext; 61 private final int mSubId; 62 private final boolean mGain; 63 64 /** 65 * Construct a listener that will wait for adding or removing carrier privileges. 66 * 67 * @param subId the subId to wait for. 68 * @param hash the package hash that indicate carrier privileges. 69 * @param gain if true, wait for the package to be added; if false, wait for the package to 70 * be removed. 71 */ CarrierPrivilegeReceiver(Context c, int subId, String hash, boolean gain)72 CarrierPrivilegeReceiver(Context c, int subId, String hash, boolean gain) { 73 mContext = c; 74 mSubId = subId; 75 mGain = gain; 76 77 mContext.registerReceiver( 78 this, new IntentFilter(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)); 79 } 80 81 @Override close()82 public void close() { 83 mContext.unregisterReceiver(this); 84 } 85 86 @Override onReceive(Context c, Intent intent)87 public void onReceive(Context c, Intent intent) { 88 if (!CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED.equals(intent.getAction())) { 89 return; 90 } 91 92 final int subId = intent.getIntExtra( 93 CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX, 94 SubscriptionManager.INVALID_SUBSCRIPTION_ID); 95 if (mSubId != subId) { 96 return; 97 } 98 99 final PersistableBundle carrierConfigs = 100 c.getSystemService(CarrierConfigManager.class).getConfigForSubId(mSubId); 101 if (!CarrierConfigManager.isConfigForIdentifiedCarrier(carrierConfigs)) { 102 return; 103 } 104 105 try { 106 if (hasCarrierPrivileges(c, mSubId) == mGain) { 107 mLatch.countDown(); 108 } 109 } catch (Exception e) { 110 } 111 } 112 waitForCarrierPrivilegeChanged()113 public void waitForCarrierPrivilegeChanged() throws Exception { 114 if (!mLatch.await(5000 /* millis */, TimeUnit.MILLISECONDS)) { 115 throw new IllegalStateException("Failed to update carrier privileges"); 116 } 117 } 118 } 119 hasCarrierPrivileges(Context c, int subId)120 private static boolean hasCarrierPrivileges(Context c, int subId) { 121 // Synchronously check for carrier privileges. Checking certificates MAY be incorrect if 122 // broadcasts are delayed. 123 return c.getSystemService(TelephonyManager.class) 124 .createForSubscriptionId(subId) 125 .hasCarrierPrivileges(); 126 } 127 getCertHashForThisPackage(final Context c)128 private static String getCertHashForThisPackage(final Context c) throws Exception { 129 final PackageInfo pkgInfo = c.getPackageManager() 130 .getPackageInfo(c.getOpPackageName(), PackageManager.GET_SIGNATURES); 131 final MessageDigest md = MessageDigest.getInstance("SHA-256"); 132 final byte[] certHash = md.digest(pkgInfo.signatures[0].toByteArray()); 133 return IccUtils.bytesToHexString(certHash); 134 } 135 changeCarrierPrivileges(Context c, int subId, boolean gain, boolean isShell)136 private static void changeCarrierPrivileges(Context c, int subId, boolean gain, boolean isShell) 137 throws Exception { 138 if (hasCarrierPrivileges(c, subId) == gain) { 139 Log.w(TAG, "Carrier privileges already " + (gain ? "granted" : "revoked") + "; bug?"); 140 return; 141 } 142 143 final String certHash = getCertHashForThisPackage(c); 144 final PersistableBundle carrierConfigs; 145 146 if (gain) { 147 carrierConfigs = new PersistableBundle(); 148 carrierConfigs.putStringArray( 149 CarrierConfigManager.KEY_CARRIER_CERTIFICATE_STRING_ARRAY, 150 new String[] {certHash}); 151 } else { 152 carrierConfigs = null; 153 } 154 155 final CarrierConfigManager configManager = c.getSystemService(CarrierConfigManager.class); 156 157 try (CarrierPrivilegeReceiver receiver = 158 new CarrierPrivilegeReceiver(c, subId, certHash, gain)) { 159 // If the caller is the shell, it's dangerous to adopt shell permission identity for 160 // the CarrierConfig override (as it will override the existing shell permissions). 161 if (isShell) { 162 configManager.overrideConfig(subId, carrierConfigs); 163 } else { 164 runWithShellPermissionIdentity(() -> { 165 configManager.overrideConfig(subId, carrierConfigs); 166 }, android.Manifest.permission.MODIFY_PHONE_STATE); 167 } 168 169 receiver.waitForCarrierPrivilegeChanged(); 170 } 171 } 172 withCarrierPrivileges(Context c, int subId, ThrowingRunnable action)173 public static void withCarrierPrivileges(Context c, int subId, ThrowingRunnable action) 174 throws Exception { 175 try { 176 changeCarrierPrivileges(c, subId, true /* gain */, false /* isShell */); 177 action.runOrThrow(); 178 } finally { 179 changeCarrierPrivileges(c, subId, false /* lose */, false /* isShell */); 180 } 181 } 182 183 /** Completes the provided action while assuming the caller is the Shell. */ withCarrierPrivilegesForShell(Context c, int subId, ThrowingRunnable action)184 public static void withCarrierPrivilegesForShell(Context c, int subId, ThrowingRunnable action) 185 throws Exception { 186 try { 187 changeCarrierPrivileges(c, subId, true /* gain */, true /* isShell */); 188 action.runOrThrow(); 189 } finally { 190 changeCarrierPrivileges(c, subId, false /* lose */, true /* isShell */); 191 } 192 } 193 withCarrierPrivileges(Context c, int subId, ThrowingSupplier<R> action)194 public static <R> R withCarrierPrivileges(Context c, int subId, ThrowingSupplier<R> action) 195 throws Exception { 196 try { 197 changeCarrierPrivileges(c, subId, true /* gain */, false /* isShell */); 198 return action.getOrThrow(); 199 } finally { 200 changeCarrierPrivileges(c, subId, false /* lose */, false /* isShell */); 201 } 202 } 203 } 204