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                 configManager.notifyConfigChangedForSubId(subId);
164             } else {
165                 runWithShellPermissionIdentity(() -> {
166                     configManager.overrideConfig(subId, carrierConfigs);
167                     configManager.notifyConfigChangedForSubId(subId);
168                 }, android.Manifest.permission.MODIFY_PHONE_STATE);
169             }
170 
171             receiver.waitForCarrierPrivilegeChanged();
172         }
173     }
174 
withCarrierPrivileges(Context c, int subId, ThrowingRunnable action)175     public static void withCarrierPrivileges(Context c, int subId, ThrowingRunnable action)
176             throws Exception {
177         try {
178             changeCarrierPrivileges(c, subId, true /* gain */, false /* isShell */);
179             action.runOrThrow();
180         } finally {
181             changeCarrierPrivileges(c, subId, false /* lose */, false /* isShell */);
182         }
183     }
184 
185     /** Completes the provided action while assuming the caller is the Shell. */
withCarrierPrivilegesForShell(Context c, int subId, ThrowingRunnable action)186     public static void withCarrierPrivilegesForShell(Context c, int subId, ThrowingRunnable action)
187             throws Exception {
188         try {
189             changeCarrierPrivileges(c, subId, true /* gain */, true /* isShell */);
190             action.runOrThrow();
191         } finally {
192             changeCarrierPrivileges(c, subId, false /* lose */, true /* isShell */);
193         }
194     }
195 
withCarrierPrivileges(Context c, int subId, ThrowingSupplier<R> action)196     public static <R> R withCarrierPrivileges(Context c, int subId, ThrowingSupplier<R> action)
197             throws Exception {
198         try {
199             changeCarrierPrivileges(c, subId, true /* gain */, false /* isShell */);
200             return action.getOrThrow();
201         } finally {
202             changeCarrierPrivileges(c, subId, false /* lose */, false /* isShell */);
203         }
204     }
205 }
206