1 /*
2  * Copyright (C) 2016 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.server.wifi;
18 
19 import android.annotation.IntDef;
20 import android.annotation.NonNull;
21 import android.app.AlertDialog;
22 import android.app.Notification;
23 import android.app.NotificationManager;
24 import android.app.PendingIntent;
25 import android.content.BroadcastReceiver;
26 import android.content.Context;
27 import android.content.Intent;
28 import android.content.IntentFilter;
29 import android.content.res.Resources;
30 import android.database.ContentObserver;
31 import android.graphics.drawable.Icon;
32 import android.net.Uri;
33 import android.net.wifi.WifiConfiguration;
34 import android.net.wifi.WifiEnterpriseConfig;
35 import android.net.wifi.hotspot2.PasspointConfiguration;
36 import android.net.wifi.hotspot2.pps.Credential;
37 import android.os.Handler;
38 import android.os.PersistableBundle;
39 import android.telephony.CarrierConfigManager;
40 import android.telephony.ImsiEncryptionInfo;
41 import android.telephony.SubscriptionInfo;
42 import android.telephony.SubscriptionManager;
43 import android.telephony.TelephonyManager;
44 import android.text.TextUtils;
45 import android.util.Base64;
46 import android.util.Log;
47 import android.util.Pair;
48 import android.util.SparseBooleanArray;
49 import android.view.WindowManager;
50 
51 import com.android.internal.annotations.VisibleForTesting;
52 import com.android.internal.messages.nano.SystemMessageProto;
53 import com.android.wifi.resources.R;
54 
55 import java.io.FileDescriptor;
56 import java.io.PrintWriter;
57 import java.lang.annotation.Retention;
58 import java.lang.annotation.RetentionPolicy;
59 import java.security.InvalidKeyException;
60 import java.security.NoSuchAlgorithmException;
61 import java.security.PublicKey;
62 import java.util.ArrayList;
63 import java.util.HashMap;
64 import java.util.List;
65 import java.util.Map;
66 
67 import javax.annotation.Nullable;
68 import javax.crypto.BadPaddingException;
69 import javax.crypto.Cipher;
70 import javax.crypto.IllegalBlockSizeException;
71 import javax.crypto.NoSuchPaddingException;
72 
73 /**
74  * This class provide APIs to get carrier info from telephony service.
75  * TODO(b/132188983): Refactor into TelephonyFacade which owns all instances of
76  *  TelephonyManager/SubscriptionManager in Wifi
77  */
78 public class WifiCarrierInfoManager {
79     public static final String TAG = "WifiCarrierInfoManager";
80     public static final String DEFAULT_EAP_PREFIX = "\0";
81 
82     public static final int CARRIER_INVALID_TYPE = -1;
83     public static final int CARRIER_MNO_TYPE = 0; // Mobile Network Operator
84     public static final int CARRIER_MVNO_TYPE = 1; // Mobile Virtual Network Operator
85     public static final String ANONYMOUS_IDENTITY = "anonymous";
86     public static final String THREE_GPP_NAI_REALM_FORMAT = "wlan.mnc%s.mcc%s.3gppnetwork.org";
87     /** Intent when user tapped action button to allow the app. */
88     @VisibleForTesting
89     public static final String NOTIFICATION_USER_ALLOWED_CARRIER_INTENT_ACTION =
90             "com.android.server.wifi.action.CarrierNetwork.USER_ALLOWED_CARRIER";
91     /** Intent when user tapped action button to disallow the app. */
92     @VisibleForTesting
93     public static final String NOTIFICATION_USER_DISALLOWED_CARRIER_INTENT_ACTION =
94             "com.android.server.wifi.action.CarrierNetwork.USER_DISALLOWED_CARRIER";
95     /** Intent when user dismissed the notification. */
96     @VisibleForTesting
97     public static final String NOTIFICATION_USER_DISMISSED_INTENT_ACTION =
98             "com.android.server.wifi.action.CarrierNetwork.USER_DISMISSED";
99     @VisibleForTesting
100     public static final String EXTRA_CARRIER_NAME =
101             "com.android.server.wifi.extra.CarrierNetwork.CARRIER_NAME";
102     @VisibleForTesting
103     public static final String EXTRA_CARRIER_ID =
104             "com.android.server.wifi.extra.CarrierNetwork.CARRIER_ID";
105 
106     // IMSI encryption method: RSA-OAEP with SHA-256 hash function
107     private static final String IMSI_CIPHER_TRANSFORMATION =
108             "RSA/ECB/OAEPwithSHA-256andMGF1Padding";
109 
110     private static final HashMap<Integer, String> EAP_METHOD_PREFIX = new HashMap<>();
111     static {
EAP_METHOD_PREFIX.put(WifiEnterpriseConfig.Eap.AKA, "0")112         EAP_METHOD_PREFIX.put(WifiEnterpriseConfig.Eap.AKA, "0");
EAP_METHOD_PREFIX.put(WifiEnterpriseConfig.Eap.SIM, "1")113         EAP_METHOD_PREFIX.put(WifiEnterpriseConfig.Eap.SIM, "1");
EAP_METHOD_PREFIX.put(WifiEnterpriseConfig.Eap.AKA_PRIME, "6")114         EAP_METHOD_PREFIX.put(WifiEnterpriseConfig.Eap.AKA_PRIME, "6");
115     }
116 
117     public static final int ACTION_USER_ALLOWED_CARRIER = 1;
118     public static final int ACTION_USER_DISALLOWED_CARRIER = 2;
119     public static final int ACTION_USER_DISMISS = 3;
120 
121     @IntDef(prefix = { "ACTION_USER_" }, value = {
122             ACTION_USER_ALLOWED_CARRIER,
123             ACTION_USER_DISALLOWED_CARRIER,
124             ACTION_USER_DISMISS
125     })
126     @Retention(RetentionPolicy.SOURCE)
127     public @interface UserActionCode { }
128 
129     /**
130      * 3GPP TS 11.11  2G_authentication command/response
131      *                Input: [RAND]
132      *                Output: [SRES][Cipher Key Kc]
133      */
134     private static final int START_SRES_POS = 0; // MUST be 0
135     private static final int SRES_LEN = 4;
136     private static final int START_KC_POS = START_SRES_POS + SRES_LEN;
137     private static final int KC_LEN = 8;
138 
139     private static final Uri CONTENT_URI = Uri.parse("content://carrier_information/carrier");
140 
141     private final WifiContext mContext;
142     private final Handler mHandler;
143     private final WifiInjector mWifiInjector;
144     private final Resources mResources;
145     private final TelephonyManager mTelephonyManager;
146     private final SubscriptionManager mSubscriptionManager;
147     private final NotificationManager mNotificationManager;
148     private final WifiMetrics mWifiMetrics;
149 
150     /**
151      * Intent filter for processing notification actions.
152      */
153     private final IntentFilter mIntentFilter;
154     private final FrameworkFacade mFrameworkFacade;
155 
156     private boolean mVerboseLogEnabled = false;
157     private SparseBooleanArray mImsiEncryptionRequired = new SparseBooleanArray();
158     private SparseBooleanArray mImsiEncryptionInfoAvailable = new SparseBooleanArray();
159     private SparseBooleanArray mEapMethodPrefixEnable = new SparseBooleanArray();
160     private final Map<Integer, Boolean> mImsiPrivacyProtectionExemptionMap = new HashMap<>();
161     private final List<OnUserApproveCarrierListener>
162             mOnUserApproveCarrierListeners =
163             new ArrayList<>();
164 
165     private boolean mUserApprovalUiActive = false;
166     private boolean mHasNewDataToSerialize = false;
167     private boolean mUserDataLoaded = false;
168     private boolean mIsLastUserApprovalUiDialog = false;
169 
170     /**
171      * Interface for other modules to listen to the user approve IMSI protection exemption.
172      */
173     public interface OnUserApproveCarrierListener {
174 
175         /**
176          * Invoke when user approve the IMSI protection exemption.
177          */
onUserAllowed(int carrierId)178         void onUserAllowed(int carrierId);
179     }
180 
181     /**
182      * Module to interact with the wifi config store.
183      */
184     private class ImsiProtectionExemptionDataSource implements
185             ImsiPrivacyProtectionExemptionStoreData.DataSource {
186         @Override
toSerialize()187         public Map<Integer, Boolean> toSerialize() {
188             // Clear the flag after writing to disk.
189             // TODO(b/115504887): Don't reset the flag on write failure.
190             mHasNewDataToSerialize = false;
191             return mImsiPrivacyProtectionExemptionMap;
192         }
193 
194         @Override
fromDeserialized(Map<Integer, Boolean> imsiProtectionExemptionMap)195         public void fromDeserialized(Map<Integer, Boolean> imsiProtectionExemptionMap) {
196             mImsiPrivacyProtectionExemptionMap.clear();
197             mImsiPrivacyProtectionExemptionMap.putAll(imsiProtectionExemptionMap);
198             mUserDataLoaded = true;
199         }
200 
201         @Override
reset()202         public void reset() {
203             mUserDataLoaded = false;
204             mImsiPrivacyProtectionExemptionMap.clear();
205         }
206 
207         @Override
hasNewDataToSerialize()208         public boolean hasNewDataToSerialize() {
209             return mHasNewDataToSerialize;
210         }
211     }
212 
213     private final BroadcastReceiver mBroadcastReceiver =
214             new BroadcastReceiver() {
215                 @Override
216                 public void onReceive(Context context, Intent intent) {
217                     String carrierName = intent.getStringExtra(EXTRA_CARRIER_NAME);
218                     int carrierId = intent.getIntExtra(EXTRA_CARRIER_ID, -1);
219                     if (carrierName == null || carrierId == -1) {
220                         Log.e(TAG, "No carrier name or carrier id found in intent");
221                         return;
222                     }
223 
224                     switch (intent.getAction()) {
225                         case NOTIFICATION_USER_ALLOWED_CARRIER_INTENT_ACTION:
226                             Log.i(TAG, "User clicked to allow carrier");
227                             sendImsiPrivacyConfirmationDialog(carrierName, carrierId);
228                             // Collapse the notification bar
229                             mContext.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
230                             break;
231                         case NOTIFICATION_USER_DISALLOWED_CARRIER_INTENT_ACTION:
232                             handleUserDisallowCarrierExemptionAction(carrierName, carrierId);
233                             break;
234                         case NOTIFICATION_USER_DISMISSED_INTENT_ACTION:
235                             handleUserDismissAction();
236                             return; // no need to cancel a dismissed notification, return.
237                         default:
238                             Log.e(TAG, "Unknown action " + intent.getAction());
239                             return;
240                     }
241                     // Clear notification once the user interacts with it.
242                     mNotificationManager.cancel(SystemMessageProto
243                             .SystemMessage.NOTE_NETWORK_SUGGESTION_AVAILABLE);
244                 }
245             };
handleUserDismissAction()246     private void handleUserDismissAction() {
247         Log.i(TAG, "User dismissed the notification");
248         mUserApprovalUiActive = false;
249         mWifiMetrics.addUserApprovalCarrierUiReaction(ACTION_USER_DISMISS,
250                 mIsLastUserApprovalUiDialog);
251     }
252 
handleUserAllowCarrierExemptionAction(String carrierName, int carrierId)253     private void handleUserAllowCarrierExemptionAction(String carrierName, int carrierId) {
254         Log.i(TAG, "User clicked to allow carrier:" + carrierName);
255         setHasUserApprovedImsiPrivacyExemptionForCarrier(true, carrierId);
256         mUserApprovalUiActive = false;
257         mWifiMetrics.addUserApprovalCarrierUiReaction(ACTION_USER_ALLOWED_CARRIER,
258                 mIsLastUserApprovalUiDialog);
259 
260     }
261 
handleUserDisallowCarrierExemptionAction(String carrierName, int carrierId)262     private void handleUserDisallowCarrierExemptionAction(String carrierName, int carrierId) {
263         Log.i(TAG, "User clicked to disallow carrier:" + carrierName);
264         setHasUserApprovedImsiPrivacyExemptionForCarrier(false, carrierId);
265         mUserApprovalUiActive = false;
266         mWifiMetrics.addUserApprovalCarrierUiReaction(
267                 ACTION_USER_DISALLOWED_CARRIER, mIsLastUserApprovalUiDialog);
268     }
269 
270     /**
271      * Gets the instance of WifiCarrierInfoManager.
272      * @param telephonyManager Instance of {@link TelephonyManager}
273      * @param subscriptionManager Instance of {@link SubscriptionManager}
274      * @param WifiInjector Instance of {@link WifiInjector}
275      * @return The instance of WifiCarrierInfoManager
276      */
WifiCarrierInfoManager(@onNull TelephonyManager telephonyManager, @NonNull SubscriptionManager subscriptionManager, @NonNull WifiInjector wifiInjector, @NonNull FrameworkFacade frameworkFacade, @NonNull WifiContext context, @NonNull WifiConfigStore configStore, @NonNull Handler handler, @NonNull WifiMetrics wifiMetrics)277     public WifiCarrierInfoManager(@NonNull TelephonyManager telephonyManager,
278             @NonNull SubscriptionManager subscriptionManager,
279             @NonNull WifiInjector wifiInjector,
280             @NonNull FrameworkFacade frameworkFacade,
281             @NonNull WifiContext context,
282             @NonNull WifiConfigStore configStore,
283             @NonNull Handler handler,
284             @NonNull WifiMetrics wifiMetrics) {
285         mTelephonyManager = telephonyManager;
286         mContext = context;
287         mResources = mContext.getResources();
288         mWifiInjector = wifiInjector;
289         mHandler = handler;
290         mSubscriptionManager = subscriptionManager;
291         mFrameworkFacade = frameworkFacade;
292         mWifiMetrics = wifiMetrics;
293         mNotificationManager =
294                 (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
295         // Register broadcast receiver for UI interactions.
296         mIntentFilter = new IntentFilter();
297         mIntentFilter.addAction(NOTIFICATION_USER_DISMISSED_INTENT_ACTION);
298         mIntentFilter.addAction(NOTIFICATION_USER_ALLOWED_CARRIER_INTENT_ACTION);
299         mIntentFilter.addAction(NOTIFICATION_USER_DISALLOWED_CARRIER_INTENT_ACTION);
300 
301         mContext.registerReceiver(mBroadcastReceiver, mIntentFilter, null, handler);
302         configStore.registerStoreData(wifiInjector.makeImsiProtectionExemptionStoreData(
303                 new ImsiProtectionExemptionDataSource()));
304 
305         updateImsiEncryptionInfo(context);
306 
307         // Monitor for carrier config changes.
308         IntentFilter filter = new IntentFilter();
309         filter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
310         context.registerReceiver(new BroadcastReceiver() {
311             @Override
312             public void onReceive(Context context, Intent intent) {
313                 if (CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED
314                         .equals(intent.getAction())) {
315                     updateImsiEncryptionInfo(context);
316                 }
317             }
318         }, filter);
319 
320         frameworkFacade.registerContentObserver(context, CONTENT_URI, false,
321                 new ContentObserver(handler) {
322                     @Override
323                     public void onChange(boolean selfChange) {
324                         updateImsiEncryptionInfo(context);
325                     }
326                 });
327     }
328 
329     /**
330      * Enable/disable verbose logging.
331      */
enableVerboseLogging(int verbose)332     public void enableVerboseLogging(int verbose) {
333         mVerboseLogEnabled = verbose > 0;
334     }
335 
336     /**
337      * Updates the IMSI encryption information.
338      */
updateImsiEncryptionInfo(Context context)339     private void updateImsiEncryptionInfo(Context context) {
340         CarrierConfigManager carrierConfigManager =
341                 (CarrierConfigManager) context.getSystemService(Context.CARRIER_CONFIG_SERVICE);
342         if (carrierConfigManager == null) {
343             return;
344         }
345 
346         mImsiEncryptionRequired.clear();
347         mImsiEncryptionInfoAvailable.clear();
348         mEapMethodPrefixEnable.clear();
349         List<SubscriptionInfo> activeSubInfos =
350                 mSubscriptionManager.getActiveSubscriptionInfoList();
351         if (activeSubInfos == null) {
352             return;
353         }
354         for (SubscriptionInfo subInfo : activeSubInfos) {
355             int subId = subInfo.getSubscriptionId();
356             PersistableBundle bundle = carrierConfigManager.getConfigForSubId(subId);
357             if (bundle != null) {
358                 if ((bundle.getInt(CarrierConfigManager.IMSI_KEY_AVAILABILITY_INT)
359                                     & TelephonyManager.KEY_TYPE_WLAN) != 0) {
360                     vlogd("IMSI encryption is required for " + subId);
361                     mImsiEncryptionRequired.put(subId, true);
362                 }
363                 if (bundle.getBoolean(CarrierConfigManager.ENABLE_EAP_METHOD_PREFIX_BOOL)) {
364                     vlogd("EAP Prefix is required for " + subId);
365                     mEapMethodPrefixEnable.put(subId, true);
366                 }
367             } else {
368                 Log.e(TAG, "Carrier config is missing for: " + subId);
369             }
370 
371             try {
372                 if (mImsiEncryptionRequired.get(subId)
373                         && mTelephonyManager.createForSubscriptionId(subId)
374                         .getCarrierInfoForImsiEncryption(TelephonyManager.KEY_TYPE_WLAN) != null) {
375                     vlogd("IMSI encryption info is available for " + subId);
376                     mImsiEncryptionInfoAvailable.put(subId, true);
377                 }
378             } catch (IllegalArgumentException e) {
379                 vlogd("IMSI encryption info is not available.");
380             }
381         }
382     }
383 
384     /**
385      * Check if the IMSI encryption is required for the SIM card.
386      *
387      * @param subId The subscription ID of SIM card.
388      * @return true if the IMSI encryption is required, otherwise false.
389      */
requiresImsiEncryption(int subId)390     public boolean requiresImsiEncryption(int subId) {
391         return mImsiEncryptionRequired.get(subId);
392     }
393 
394     /**
395      * Check if the IMSI encryption is downloaded(available) for the SIM card.
396      *
397      * @param subId The subscription ID of SIM card.
398      * @return true if the IMSI encryption is available, otherwise false.
399      */
isImsiEncryptionInfoAvailable(int subId)400     public boolean isImsiEncryptionInfoAvailable(int subId) {
401         return mImsiEncryptionInfoAvailable.get(subId);
402     }
403 
404     /**
405      * Gets the SubscriptionId of SIM card which is from the carrier specified in config.
406      *
407      * @param config the instance of {@link WifiConfiguration}
408      * @return the best match SubscriptionId
409      */
getBestMatchSubscriptionId(@onNull WifiConfiguration config)410     public int getBestMatchSubscriptionId(@NonNull WifiConfiguration config) {
411         if (config.isPasspoint()) {
412             return getMatchingSubId(config.carrierId);
413         } else {
414             return getBestMatchSubscriptionIdForEnterprise(config);
415         }
416     }
417 
418     /**
419      * Gets the SubscriptionId of SIM card for given carrier Id
420      *
421      * @param carrierId carrier id for target carrier
422      * @return the matched SubscriptionId
423      */
getMatchingSubId(int carrierId)424     public int getMatchingSubId(int carrierId) {
425         List<SubscriptionInfo> subInfoList = mSubscriptionManager.getActiveSubscriptionInfoList();
426         if (subInfoList == null || subInfoList.isEmpty()) {
427             return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
428         }
429 
430         int dataSubId = SubscriptionManager.getDefaultDataSubscriptionId();
431         int matchSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
432         for (SubscriptionInfo subInfo : subInfoList) {
433             if (subInfo.getCarrierId() == carrierId) {
434                 matchSubId = subInfo.getSubscriptionId();
435                 if (matchSubId == dataSubId) {
436                     // Priority of Data sub is higher than non data sub.
437                     break;
438                 }
439             }
440         }
441         vlogd("matching subId is " + matchSubId);
442         return matchSubId;
443     }
444 
getBestMatchSubscriptionIdForEnterprise(WifiConfiguration config)445     private int getBestMatchSubscriptionIdForEnterprise(WifiConfiguration config) {
446         if (config.carrierId != TelephonyManager.UNKNOWN_CARRIER_ID) {
447             return getMatchingSubId(config.carrierId);
448         }
449         // Legacy WifiConfiguration without carrier ID
450         if (config.enterpriseConfig == null
451                  || !config.enterpriseConfig.isAuthenticationSimBased()) {
452             Log.w(TAG, "The legacy config is not using EAP-SIM.");
453             return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
454         }
455         int dataSubId = SubscriptionManager.getDefaultDataSubscriptionId();
456         if (isSimPresent(dataSubId)) {
457             vlogd("carrierId is not assigned, using the default data sub.");
458             return dataSubId;
459         }
460         vlogd("data sim is not present.");
461         return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
462     }
463 
464     /**
465      * Check if the specified SIM card is in the device.
466      *
467      * @param subId subscription ID of SIM card in the device.
468      * @return true if the subId is active, otherwise false.
469      */
isSimPresent(int subId)470     public boolean isSimPresent(int subId) {
471         if (!SubscriptionManager.isValidSubscriptionId(subId)) {
472             return false;
473         }
474         List<SubscriptionInfo> subInfoList = mSubscriptionManager.getActiveSubscriptionInfoList();
475         if (subInfoList == null || subInfoList.isEmpty()) {
476             return false;
477         }
478         return subInfoList.stream()
479                 .anyMatch(info -> info.getSubscriptionId() == subId
480                         && isSimStateReady(info));
481     }
482 
483     /**
484      * Check if SIM card for SubscriptionInfo is ready.
485      */
isSimStateReady(SubscriptionInfo info)486     private boolean isSimStateReady(SubscriptionInfo info) {
487         int simSlotIndex = info.getSimSlotIndex();
488         return mTelephonyManager.getSimState(simSlotIndex) == TelephonyManager.SIM_STATE_READY;
489     }
490 
491     /**
492      * Get the identity for the current SIM or null if the SIM is not available
493      *
494      * @param config WifiConfiguration that indicates what sort of authentication is necessary
495      * @return Pair<identify, encrypted identity> or null if the SIM is not available
496      * or config is invalid
497      */
getSimIdentity(WifiConfiguration config)498     public Pair<String, String> getSimIdentity(WifiConfiguration config) {
499         int subId = getBestMatchSubscriptionId(config);
500         if (!SubscriptionManager.isValidSubscriptionId(subId)) {
501             return null;
502         }
503 
504         TelephonyManager specifiedTm = mTelephonyManager.createForSubscriptionId(subId);
505         String imsi = specifiedTm.getSubscriberId();
506         String mccMnc = "";
507 
508         if (specifiedTm.getSimState() == TelephonyManager.SIM_STATE_READY) {
509             mccMnc = specifiedTm.getSimOperator();
510         }
511 
512         String identity = buildIdentity(getSimMethodForConfig(config), imsi, mccMnc, false);
513         if (identity == null) {
514             Log.e(TAG, "Failed to build the identity");
515             return null;
516         }
517 
518         ImsiEncryptionInfo imsiEncryptionInfo;
519         try {
520             imsiEncryptionInfo = specifiedTm.getCarrierInfoForImsiEncryption(
521                     TelephonyManager.KEY_TYPE_WLAN);
522         } catch (RuntimeException e) {
523             Log.e(TAG, "Failed to get imsi encryption info: " + e.getMessage());
524             return null;
525         }
526         if (imsiEncryptionInfo == null) {
527             // Does not support encrypted identity.
528             return Pair.create(identity, "");
529         }
530 
531         String encryptedIdentity = buildEncryptedIdentity(identity,
532                     imsiEncryptionInfo);
533 
534         // In case of failure for encryption, abort current EAP authentication.
535         if (encryptedIdentity == null) {
536             Log.e(TAG, "failed to encrypt the identity");
537             return null;
538         }
539         return Pair.create(identity, encryptedIdentity);
540     }
541 
542     /**
543      * Gets Anonymous identity for current active SIM.
544      *
545      * @param config the instance of WifiConfiguration.
546      * @return anonymous identity@realm which is based on current MCC/MNC, {@code null} if SIM is
547      * not ready or absent.
548      */
getAnonymousIdentityWith3GppRealm(@onNull WifiConfiguration config)549     public String getAnonymousIdentityWith3GppRealm(@NonNull WifiConfiguration config) {
550         int subId = getBestMatchSubscriptionId(config);
551         TelephonyManager specifiedTm = mTelephonyManager.createForSubscriptionId(subId);
552         if (specifiedTm.getSimState() != TelephonyManager.SIM_STATE_READY) {
553             return null;
554         }
555         String mccMnc = specifiedTm.getSimOperator();
556         if (mccMnc == null || mccMnc.isEmpty()) {
557             return null;
558         }
559 
560         // Extract mcc & mnc from mccMnc
561         String mcc = mccMnc.substring(0, 3);
562         String mnc = mccMnc.substring(3);
563 
564         if (mnc.length() == 2) {
565             mnc = "0" + mnc;
566         }
567 
568         String realm = String.format(THREE_GPP_NAI_REALM_FORMAT, mnc, mcc);
569         StringBuilder sb = new StringBuilder();
570         if (mEapMethodPrefixEnable.get(subId)) {
571             // Set the EAP method as a prefix
572             String eapMethod = EAP_METHOD_PREFIX.get(config.enterpriseConfig.getEapMethod());
573             if (!TextUtils.isEmpty(eapMethod)) {
574                 sb.append(eapMethod);
575             }
576         }
577         return sb.append(ANONYMOUS_IDENTITY).append("@").append(realm).toString();
578     }
579 
580     /**
581      * Encrypt the given data with the given public key.  The encrypted data will be returned as
582      * a Base64 encoded string.
583      *
584      * @param key The public key to use for encryption
585      * @param data The data need to be encrypted
586      * @param encodingFlag base64 encoding flag
587      * @return Base64 encoded string, or null if encryption failed
588      */
589     @VisibleForTesting
encryptDataUsingPublicKey(PublicKey key, byte[] data, int encodingFlag)590     public static String encryptDataUsingPublicKey(PublicKey key, byte[] data, int encodingFlag) {
591         try {
592             Cipher cipher = Cipher.getInstance(IMSI_CIPHER_TRANSFORMATION);
593             cipher.init(Cipher.ENCRYPT_MODE, key);
594             byte[] encryptedBytes = cipher.doFinal(data);
595 
596             return Base64.encodeToString(encryptedBytes, 0, encryptedBytes.length, encodingFlag);
597         } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException
598                 | IllegalBlockSizeException | BadPaddingException e) {
599             Log.e(TAG, "Encryption failed: " + e.getMessage());
600             return null;
601         }
602     }
603 
604     /**
605      * Create the encrypted identity.
606      *
607      * Prefix value:
608      * "0" - EAP-AKA Identity
609      * "1" - EAP-SIM Identity
610      * "6" - EAP-AKA' Identity
611      * Encrypted identity format: prefix|IMSI@<NAIRealm>
612      * @param identity           permanent identity with format based on section 4.1.1.6 of RFC 4187
613      *                           and 4.2.1.6 of RFC 4186.
614      * @param imsiEncryptionInfo The IMSI encryption info retrieved from the SIM
615      * @return "\0" + encryptedIdentity + "{, Key Identifier AVP}"
616      */
buildEncryptedIdentity(String identity, ImsiEncryptionInfo imsiEncryptionInfo)617     private static String buildEncryptedIdentity(String identity,
618             ImsiEncryptionInfo imsiEncryptionInfo) {
619         if (imsiEncryptionInfo == null) {
620             Log.e(TAG, "imsiEncryptionInfo is not valid");
621             return null;
622         }
623         if (identity == null) {
624             Log.e(TAG, "identity is not valid");
625             return null;
626         }
627 
628         // Build and return the encrypted identity.
629         String encryptedIdentity = encryptDataUsingPublicKey(
630                 imsiEncryptionInfo.getPublicKey(), identity.getBytes(), Base64.NO_WRAP);
631         if (encryptedIdentity == null) {
632             Log.e(TAG, "Failed to encrypt IMSI");
633             return null;
634         }
635         encryptedIdentity = DEFAULT_EAP_PREFIX + encryptedIdentity;
636         if (imsiEncryptionInfo.getKeyIdentifier() != null) {
637             // Include key identifier AVP (Attribute Value Pair).
638             encryptedIdentity = encryptedIdentity + "," + imsiEncryptionInfo.getKeyIdentifier();
639         }
640         return encryptedIdentity;
641     }
642 
643     /**
644      * Create an identity used for SIM-based EAP authentication. The identity will be based on
645      * the info retrieved from the SIM card, such as IMSI and IMSI encryption info. The IMSI
646      * contained in the identity will be encrypted if IMSI encryption info is provided.
647      *
648      * See  rfc4186 & rfc4187 & rfc5448:
649      *
650      * Identity format:
651      * Prefix | [IMSI || Encrypted IMSI] | @realm | {, Key Identifier AVP}
652      * where "|" denotes concatenation, "||" denotes exclusive value, "{}"
653      * denotes optional value, and realm is the 3GPP network domain name derived from the given
654      * MCC/MNC according to the 3GGP spec(TS23.003).
655      *
656      * Prefix value:
657      * "\0" - Encrypted Identity
658      * "0" - EAP-AKA Identity
659      * "1" - EAP-SIM Identity
660      * "6" - EAP-AKA' Identity
661      *
662      * Encrypted IMSI:
663      * Base64{RSA_Public_Key_Encryption{eapPrefix | IMSI}}
664      * where "|" denotes concatenation,
665      *
666      * @param eapMethod EAP authentication method: EAP-SIM, EAP-AKA, EAP-AKA'
667      * @param imsi The IMSI retrieved from the SIM
668      * @param mccMnc The MCC MNC identifier retrieved from the SIM
669      * @param isEncrypted Whether the imsi is encrypted or not.
670      * @return the eap identity, built using either the encrypted or un-encrypted IMSI.
671      */
buildIdentity(int eapMethod, String imsi, String mccMnc, boolean isEncrypted)672     private static String buildIdentity(int eapMethod, String imsi, String mccMnc,
673                                         boolean isEncrypted) {
674         if (imsi == null || imsi.isEmpty()) {
675             Log.e(TAG, "No IMSI or IMSI is null");
676             return null;
677         }
678 
679         String prefix = isEncrypted ? DEFAULT_EAP_PREFIX : EAP_METHOD_PREFIX.get(eapMethod);
680         if (prefix == null) {
681             return null;
682         }
683 
684         /* extract mcc & mnc from mccMnc */
685         String mcc;
686         String mnc;
687         if (mccMnc != null && !mccMnc.isEmpty()) {
688             mcc = mccMnc.substring(0, 3);
689             mnc = mccMnc.substring(3);
690             if (mnc.length() == 2) {
691                 mnc = "0" + mnc;
692             }
693         } else {
694             // extract mcc & mnc from IMSI, assume mnc size is 3
695             mcc = imsi.substring(0, 3);
696             mnc = imsi.substring(3, 6);
697         }
698 
699         String naiRealm = String.format(THREE_GPP_NAI_REALM_FORMAT, mnc, mcc);
700         return prefix + imsi + "@" + naiRealm;
701     }
702 
703     /**
704      * Return the associated SIM method for the configuration.
705      *
706      * @param config WifiConfiguration corresponding to the network.
707      * @return the outer EAP method associated with this SIM configuration.
708      */
getSimMethodForConfig(WifiConfiguration config)709     private static int getSimMethodForConfig(WifiConfiguration config) {
710         if (config == null || config.enterpriseConfig == null
711                 || !config.enterpriseConfig.isAuthenticationSimBased()) {
712             return WifiEnterpriseConfig.Eap.NONE;
713         }
714         int eapMethod = config.enterpriseConfig.getEapMethod();
715         if (eapMethod == WifiEnterpriseConfig.Eap.PEAP) {
716             // Translate known inner eap methods into an equivalent outer eap method.
717             switch (config.enterpriseConfig.getPhase2Method()) {
718                 case WifiEnterpriseConfig.Phase2.SIM:
719                     eapMethod = WifiEnterpriseConfig.Eap.SIM;
720                     break;
721                 case WifiEnterpriseConfig.Phase2.AKA:
722                     eapMethod = WifiEnterpriseConfig.Eap.AKA;
723                     break;
724                 case WifiEnterpriseConfig.Phase2.AKA_PRIME:
725                     eapMethod = WifiEnterpriseConfig.Eap.AKA_PRIME;
726                     break;
727             }
728         }
729 
730         return eapMethod;
731     }
732 
733     /**
734      * Returns true if {@code identity} contains an anonymous@realm identity, false otherwise.
735      */
isAnonymousAtRealmIdentity(String identity)736     public static boolean isAnonymousAtRealmIdentity(String identity) {
737         if (TextUtils.isEmpty(identity)) return false;
738         final String anonymousId = WifiCarrierInfoManager.ANONYMOUS_IDENTITY + "@";
739         return identity.startsWith(anonymousId)
740                 || identity.substring(1).startsWith(anonymousId);
741     }
742 
743     // TODO replace some of this code with Byte.parseByte
parseHex(char ch)744     private static int parseHex(char ch) {
745         if ('0' <= ch && ch <= '9') {
746             return ch - '0';
747         } else if ('a' <= ch && ch <= 'f') {
748             return ch - 'a' + 10;
749         } else if ('A' <= ch && ch <= 'F') {
750             return ch - 'A' + 10;
751         } else {
752             throw new NumberFormatException("" + ch + " is not a valid hex digit");
753         }
754     }
755 
parseHex(String hex)756     private static byte[] parseHex(String hex) {
757         /* This only works for good input; don't throw bad data at it */
758         if (hex == null) {
759             return new byte[0];
760         }
761 
762         if (hex.length() % 2 != 0) {
763             throw new NumberFormatException(hex + " is not a valid hex string");
764         }
765 
766         byte[] result = new byte[(hex.length()) / 2 + 1];
767         result[0] = (byte) ((hex.length()) / 2);
768         for (int i = 0, j = 1; i < hex.length(); i += 2, j++) {
769             int val = parseHex(hex.charAt(i)) * 16 + parseHex(hex.charAt(i + 1));
770             byte b = (byte) (val & 0xFF);
771             result[j] = b;
772         }
773 
774         return result;
775     }
776 
parseHexWithoutLength(String hex)777     private static byte[] parseHexWithoutLength(String hex) {
778         byte[] tmpRes = parseHex(hex);
779         if (tmpRes.length == 0) {
780             return tmpRes;
781         }
782 
783         byte[] result = new byte[tmpRes.length - 1];
784         System.arraycopy(tmpRes, 1, result, 0, tmpRes.length - 1);
785 
786         return result;
787     }
788 
makeHex(byte[] bytes)789     private static String makeHex(byte[] bytes) {
790         StringBuilder sb = new StringBuilder();
791         for (byte b : bytes) {
792             sb.append(String.format("%02x", b));
793         }
794         return sb.toString();
795     }
796 
makeHex(byte[] bytes, int from, int len)797     private static String makeHex(byte[] bytes, int from, int len) {
798         StringBuilder sb = new StringBuilder();
799         for (int i = 0; i < len; i++) {
800             sb.append(String.format("%02x", bytes[from + i]));
801         }
802         return sb.toString();
803     }
804 
concatHex(byte[] array1, byte[] array2)805     private static byte[] concatHex(byte[] array1, byte[] array2) {
806 
807         int len = array1.length + array2.length;
808 
809         byte[] result = new byte[len];
810 
811         int index = 0;
812         if (array1.length != 0) {
813             for (byte b : array1) {
814                 result[index] = b;
815                 index++;
816             }
817         }
818 
819         if (array2.length != 0) {
820             for (byte b : array2) {
821                 result[index] = b;
822                 index++;
823             }
824         }
825 
826         return result;
827     }
828 
829     /**
830      * Calculate SRES and KC as 3G authentication.
831      *
832      * Standard       Cellular_auth     Type Command
833      *
834      * 3GPP TS 31.102 3G_authentication [Length][RAND][Length][AUTN]
835      *                         [Length][RES][Length][CK][Length][IK] and more
836      *
837      * @param requestData RAND data from server.
838      * @param config The instance of WifiConfiguration.
839      * @return the response data processed by SIM. If all request data is malformed, then returns
840      * empty string. If request data is invalid, then returns null.
841      */
getGsmSimAuthResponse(String[] requestData, WifiConfiguration config)842     public String getGsmSimAuthResponse(String[] requestData, WifiConfiguration config) {
843         return getGsmAuthResponseWithLength(requestData, config, TelephonyManager.APPTYPE_USIM);
844     }
845 
846     /**
847      * Calculate SRES and KC as 2G authentication.
848      *
849      * Standard       Cellular_auth     Type Command
850      *
851      * 3GPP TS 31.102 2G_authentication [Length][RAND]
852      *                         [Length][SRES][Length][Cipher Key Kc]
853      *
854      * @param requestData RAND data from server.
855      * @param config The instance of WifiConfiguration.
856      * @return the response data processed by SIM. If all request data is malformed, then returns
857      * empty string. If request data is invalid, then returns null.
858      */
getGsmSimpleSimAuthResponse(String[] requestData, WifiConfiguration config)859     public String getGsmSimpleSimAuthResponse(String[] requestData,
860             WifiConfiguration config) {
861         return getGsmAuthResponseWithLength(requestData, config, TelephonyManager.APPTYPE_SIM);
862     }
863 
getGsmAuthResponseWithLength(String[] requestData, WifiConfiguration config, int appType)864     private String getGsmAuthResponseWithLength(String[] requestData,
865             WifiConfiguration config, int appType) {
866         int subId = getBestMatchSubscriptionId(config);
867         if (!SubscriptionManager.isValidSubscriptionId(subId)) {
868             return null;
869         }
870 
871         StringBuilder sb = new StringBuilder();
872         for (String challenge : requestData) {
873             if (challenge == null || challenge.isEmpty()) {
874                 continue;
875             }
876             Log.d(TAG, "RAND = " + challenge);
877 
878             byte[] rand = null;
879             try {
880                 rand = parseHex(challenge);
881             } catch (NumberFormatException e) {
882                 Log.e(TAG, "malformed challenge");
883                 continue;
884             }
885 
886             String base64Challenge = Base64.encodeToString(rand, Base64.NO_WRAP);
887             TelephonyManager specifiedTm = mTelephonyManager.createForSubscriptionId(subId);
888             String tmResponse = specifiedTm.getIccAuthentication(
889                     appType, TelephonyManager.AUTHTYPE_EAP_SIM, base64Challenge);
890             Log.v(TAG, "Raw Response - " + tmResponse);
891 
892             if (tmResponse == null || tmResponse.length() <= 4) {
893                 Log.e(TAG, "bad response - " + tmResponse);
894                 return null;
895             }
896 
897             byte[] result = Base64.decode(tmResponse, Base64.DEFAULT);
898             Log.v(TAG, "Hex Response -" + makeHex(result));
899             int sresLen = result[0];
900             if (sresLen < 0 || sresLen >= result.length) {
901                 Log.e(TAG, "malformed response - " + tmResponse);
902                 return null;
903             }
904             String sres = makeHex(result, 1, sresLen);
905             int kcOffset = 1 + sresLen;
906             if (kcOffset >= result.length) {
907                 Log.e(TAG, "malformed response - " + tmResponse);
908                 return null;
909             }
910             int kcLen = result[kcOffset];
911             if (kcLen < 0 || kcOffset + kcLen > result.length) {
912                 Log.e(TAG, "malformed response - " + tmResponse);
913                 return null;
914             }
915             String kc = makeHex(result, 1 + kcOffset, kcLen);
916             sb.append(":" + kc + ":" + sres);
917             Log.v(TAG, "kc:" + kc + " sres:" + sres);
918         }
919 
920         return sb.toString();
921     }
922 
923     /**
924      * Calculate SRES and KC as 2G authentication.
925      *
926      * Standard       Cellular_auth     Type Command
927      *
928      * 3GPP TS 11.11  2G_authentication [RAND]
929      *                         [SRES][Cipher Key Kc]
930      *
931      * @param requestData RAND data from server.
932      * @param config the instance of WifiConfiguration.
933      * @return the response data processed by SIM. If all request data is malformed, then returns
934      * empty string. If request data is invalid, then returns null.
935      */
getGsmSimpleSimNoLengthAuthResponse(String[] requestData, @NonNull WifiConfiguration config)936     public String getGsmSimpleSimNoLengthAuthResponse(String[] requestData,
937             @NonNull WifiConfiguration config) {
938 
939         int subId = getBestMatchSubscriptionId(config);
940         if (!SubscriptionManager.isValidSubscriptionId(subId)) {
941             return null;
942         }
943 
944         StringBuilder sb = new StringBuilder();
945         for (String challenge : requestData) {
946             if (challenge == null || challenge.isEmpty()) {
947                 continue;
948             }
949             Log.d(TAG, "RAND = " + challenge);
950 
951             byte[] rand = null;
952             try {
953                 rand = parseHexWithoutLength(challenge);
954             } catch (NumberFormatException e) {
955                 Log.e(TAG, "malformed challenge");
956                 continue;
957             }
958 
959             String base64Challenge = Base64.encodeToString(rand, Base64.NO_WRAP);
960             TelephonyManager specifiedTm = mTelephonyManager.createForSubscriptionId(subId);
961             String tmResponse = specifiedTm.getIccAuthentication(TelephonyManager.APPTYPE_SIM,
962                     TelephonyManager.AUTHTYPE_EAP_SIM, base64Challenge);
963             Log.v(TAG, "Raw Response - " + tmResponse);
964 
965             if (tmResponse == null || tmResponse.length() <= 4) {
966                 Log.e(TAG, "bad response - " + tmResponse);
967                 return null;
968             }
969 
970             byte[] result = Base64.decode(tmResponse, Base64.DEFAULT);
971             if (SRES_LEN + KC_LEN != result.length) {
972                 Log.e(TAG, "malformed response - " + tmResponse);
973                 return null;
974             }
975             Log.v(TAG, "Hex Response -" + makeHex(result));
976             String sres = makeHex(result, START_SRES_POS, SRES_LEN);
977             String kc = makeHex(result, START_KC_POS, KC_LEN);
978             sb.append(":" + kc + ":" + sres);
979             Log.v(TAG, "kc:" + kc + " sres:" + sres);
980         }
981 
982         return sb.toString();
983     }
984 
985     /**
986      * Data supplied when making a SIM Auth Request
987      */
988     public static class SimAuthRequestData {
SimAuthRequestData()989         public SimAuthRequestData() {}
SimAuthRequestData(int networkId, int protocol, String ssid, String[] data)990         public SimAuthRequestData(int networkId, int protocol, String ssid, String[] data) {
991             this.networkId = networkId;
992             this.protocol = protocol;
993             this.ssid = ssid;
994             this.data = data;
995         }
996 
997         public int networkId;
998         public int protocol;
999         public String ssid;
1000         // EAP-SIM: data[] contains the 3 rand, one for each of the 3 challenges
1001         // EAP-AKA/AKA': data[] contains rand & authn couple for the single challenge
1002         public String[] data;
1003     }
1004 
1005     /**
1006      * The response to a SIM Auth request if successful
1007      */
1008     public static class SimAuthResponseData {
SimAuthResponseData(String type, String response)1009         public SimAuthResponseData(String type, String response) {
1010             this.type = type;
1011             this.response = response;
1012         }
1013 
1014         public String type;
1015         public String response;
1016     }
1017 
1018     /**
1019      * Get the response data for 3G authentication.
1020      *
1021      * @param requestData authentication request data from server.
1022      * @param config the instance of WifiConfiguration.
1023      * @return the response data processed by SIM. If request data is invalid, then returns null.
1024      */
get3GAuthResponse(SimAuthRequestData requestData, WifiConfiguration config)1025     public SimAuthResponseData get3GAuthResponse(SimAuthRequestData requestData,
1026             WifiConfiguration config) {
1027         StringBuilder sb = new StringBuilder();
1028         byte[] rand = null;
1029         byte[] authn = null;
1030         String resType = WifiNative.SIM_AUTH_RESP_TYPE_UMTS_AUTH;
1031 
1032         if (requestData.data.length == 2) {
1033             try {
1034                 rand = parseHex(requestData.data[0]);
1035                 authn = parseHex(requestData.data[1]);
1036             } catch (NumberFormatException e) {
1037                 Log.e(TAG, "malformed challenge");
1038             }
1039         } else {
1040             Log.e(TAG, "malformed challenge");
1041         }
1042 
1043         String tmResponse = "";
1044         if (rand != null && authn != null) {
1045             String base64Challenge = Base64.encodeToString(concatHex(rand, authn), Base64.NO_WRAP);
1046             int subId = getBestMatchSubscriptionId(config);
1047             if (!SubscriptionManager.isValidSubscriptionId(subId)) {
1048                 return null;
1049             }
1050             tmResponse = mTelephonyManager
1051                     .createForSubscriptionId(subId)
1052                     .getIccAuthentication(TelephonyManager.APPTYPE_USIM,
1053                             TelephonyManager.AUTHTYPE_EAP_AKA, base64Challenge);
1054             Log.v(TAG, "Raw Response - " + tmResponse);
1055         }
1056 
1057         boolean goodReponse = false;
1058         if (tmResponse != null && tmResponse.length() > 4) {
1059             byte[] result = Base64.decode(tmResponse, Base64.DEFAULT);
1060             Log.e(TAG, "Hex Response - " + makeHex(result));
1061             byte tag = result[0];
1062             if (tag == (byte) 0xdb) {
1063                 Log.v(TAG, "successful 3G authentication ");
1064                 int resLen = result[1];
1065                 String res = makeHex(result, 2, resLen);
1066                 int ckLen = result[resLen + 2];
1067                 String ck = makeHex(result, resLen + 3, ckLen);
1068                 int ikLen = result[resLen + ckLen + 3];
1069                 String ik = makeHex(result, resLen + ckLen + 4, ikLen);
1070                 sb.append(":" + ik + ":" + ck + ":" + res);
1071                 Log.v(TAG, "ik:" + ik + "ck:" + ck + " res:" + res);
1072                 goodReponse = true;
1073             } else if (tag == (byte) 0xdc) {
1074                 Log.e(TAG, "synchronisation failure");
1075                 int autsLen = result[1];
1076                 String auts = makeHex(result, 2, autsLen);
1077                 resType = WifiNative.SIM_AUTH_RESP_TYPE_UMTS_AUTS;
1078                 sb.append(":" + auts);
1079                 Log.v(TAG, "auts:" + auts);
1080                 goodReponse = true;
1081             } else {
1082                 Log.e(TAG, "bad response - unknown tag = " + tag);
1083             }
1084         } else {
1085             Log.e(TAG, "bad response - " + tmResponse);
1086         }
1087 
1088         if (goodReponse) {
1089             String response = sb.toString();
1090             Log.v(TAG, "Supplicant Response -" + response);
1091             return new SimAuthResponseData(resType, response);
1092         } else {
1093             return null;
1094         }
1095     }
1096 
1097     /**
1098      * Get the carrier type of current SIM.
1099      *
1100      * @param subId the subscription ID of SIM card.
1101      * @return carrier type of current active sim, {{@link #CARRIER_INVALID_TYPE}} if sim is not
1102      * ready.
1103      */
getCarrierType(int subId)1104     private int getCarrierType(int subId) {
1105         if (!SubscriptionManager.isValidSubscriptionId(subId)) {
1106             return CARRIER_INVALID_TYPE;
1107         }
1108         TelephonyManager specifiedTm = mTelephonyManager.createForSubscriptionId(subId);
1109 
1110         if (specifiedTm.getSimState() != TelephonyManager.SIM_STATE_READY) {
1111             return CARRIER_INVALID_TYPE;
1112         }
1113 
1114         // If two APIs return the same carrier ID, then is considered as MNO, otherwise MVNO
1115         if (specifiedTm.getCarrierIdFromSimMccMnc() == specifiedTm.getSimCarrierId()) {
1116             return CARRIER_MNO_TYPE;
1117         }
1118         return CARRIER_MVNO_TYPE;
1119     }
1120 
1121     /**
1122      * Decorates a pseudonym with the NAI realm, in case it wasn't provided by the server
1123      *
1124      * @param config The instance of WifiConfiguration
1125      * @param pseudonym The pseudonym (temporary identity) provided by the server
1126      * @return pseudonym@realm which is based on current MCC/MNC, {@code null} if SIM is
1127      * not ready or absent.
1128      */
decoratePseudonymWith3GppRealm(@onNull WifiConfiguration config, String pseudonym)1129     public String decoratePseudonymWith3GppRealm(@NonNull WifiConfiguration config,
1130             String pseudonym) {
1131         if (TextUtils.isEmpty(pseudonym)) {
1132             return null;
1133         }
1134         if (pseudonym.contains("@")) {
1135             // Pseudonym is already decorated
1136             return pseudonym;
1137         }
1138         int subId = getBestMatchSubscriptionId(config);
1139 
1140         TelephonyManager specifiedTm = mTelephonyManager.createForSubscriptionId(subId);
1141         if (specifiedTm.getSimState() != TelephonyManager.SIM_STATE_READY) {
1142             return null;
1143         }
1144         String mccMnc = specifiedTm.getSimOperator();
1145         if (mccMnc == null || mccMnc.isEmpty()) {
1146             return null;
1147         }
1148 
1149         // Extract mcc & mnc from mccMnc
1150         String mcc = mccMnc.substring(0, 3);
1151         String mnc = mccMnc.substring(3);
1152 
1153         if (mnc.length() == 2) {
1154             mnc = "0" + mnc;
1155         }
1156 
1157         String realm = String.format(THREE_GPP_NAI_REALM_FORMAT, mnc, mcc);
1158         return String.format("%s@%s", pseudonym, realm);
1159     }
1160 
1161     /**
1162      * Reset the downloaded IMSI encryption key.
1163      * @param config Instance of WifiConfiguration
1164      */
resetCarrierKeysForImsiEncryption(@onNull WifiConfiguration config)1165     public void resetCarrierKeysForImsiEncryption(@NonNull WifiConfiguration config) {
1166         int subId = getBestMatchSubscriptionId(config);
1167         if (!SubscriptionManager.isValidSubscriptionId(subId)) {
1168             return;
1169         }
1170         TelephonyManager specifiedTm = mTelephonyManager.createForSubscriptionId(subId);
1171         specifiedTm.resetCarrierKeysForImsiEncryption();
1172     }
1173 
1174     /**
1175      * Updates the carrier ID for passpoint configuration with SIM credential.
1176      *
1177      * @param config The instance of PasspointConfiguration.
1178      * @return true if the carrier ID is updated, false otherwise
1179      */
tryUpdateCarrierIdForPasspoint(PasspointConfiguration config)1180     public boolean tryUpdateCarrierIdForPasspoint(PasspointConfiguration config) {
1181         if (config.getCarrierId() != TelephonyManager.UNKNOWN_CARRIER_ID) {
1182             return false;
1183         }
1184 
1185         Credential.SimCredential simCredential = config.getCredential().getSimCredential();
1186         if (simCredential == null) {
1187             // carrier ID is not required.
1188             return false;
1189         }
1190 
1191         IMSIParameter imsiParameter = IMSIParameter.build(simCredential.getImsi());
1192         // If the IMSI is not full, the carrier ID can not be matched for sure, so it should
1193         // be ignored.
1194         if (imsiParameter == null || !imsiParameter.isFullImsi()) {
1195             vlogd("IMSI is not available or not full");
1196             return false;
1197         }
1198         List<SubscriptionInfo> infos = mSubscriptionManager.getActiveSubscriptionInfoList();
1199         if (infos == null) {
1200             return false;
1201         }
1202         // Find the active matching SIM card with the full IMSI from passpoint profile.
1203         for (SubscriptionInfo subInfo : infos) {
1204             String imsi = mTelephonyManager
1205                     .createForSubscriptionId(subInfo.getSubscriptionId()).getSubscriberId();
1206             if (imsiParameter.matchesImsi(imsi)) {
1207                 config.setCarrierId(subInfo.getCarrierId());
1208                 return true;
1209             }
1210         }
1211 
1212         return false;
1213     }
1214 
1215     /**
1216      * Get the IMSI and carrier ID of the SIM card which is matched with the given carrier ID.
1217      *
1218      * @param carrierId The carrier ID see {@link TelephonyManager.getSimCarrierId}
1219      * @return null if there is no matching SIM card, otherwise the IMSI and carrier ID of the
1220      * matching SIM card
1221      */
getMatchingImsi(int carrierId)1222     public @Nullable String getMatchingImsi(int carrierId) {
1223         int subId = getMatchingSubId(carrierId);
1224         if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
1225             if (requiresImsiEncryption(subId) && !isImsiEncryptionInfoAvailable(subId)) {
1226                 vlogd("required IMSI encryption information is not available.");
1227                 return null;
1228             }
1229             return mTelephonyManager.createForSubscriptionId(subId).getSubscriberId();
1230         }
1231         vlogd("no active SIM card to match the carrier ID.");
1232         return null;
1233     }
1234 
1235     /**
1236      * Get the IMSI and carrier ID of the SIM card which is matched with the given IMSI
1237      * (only prefix of IMSI - mccmnc*) from passpoint profile.
1238      *
1239      * @param imsiPrefix The IMSI parameter from passpoint profile.
1240      * @return null if there is no matching SIM card, otherwise the IMSI and carrier ID of the
1241      * matching SIM card
1242      */
getMatchingImsiCarrierId( String imsiPrefix)1243     public @Nullable Pair<String, Integer> getMatchingImsiCarrierId(
1244             String imsiPrefix) {
1245         IMSIParameter imsiParameter = IMSIParameter.build(imsiPrefix);
1246         if (imsiParameter == null) {
1247             return null;
1248         }
1249         List<SubscriptionInfo> infos = mSubscriptionManager.getActiveSubscriptionInfoList();
1250         if (infos == null) {
1251             return null;
1252         }
1253         int dataSubId = SubscriptionManager.getDefaultDataSubscriptionId();
1254         //Pair<IMSI, carrier ID> the IMSI and carrier ID of matched SIM card
1255         Pair<String, Integer> matchedPair = null;
1256         // matchedDataPair check if the data SIM is matched.
1257         Pair<String, Integer> matchedDataPair = null;
1258         // matchedMnoPair check if any matched SIM card is MNO.
1259         Pair<String, Integer> matchedMnoPair = null;
1260 
1261         // Find the active matched SIM card with the priority order of Data MNO SIM,
1262         // Nondata MNO SIM, Data MVNO SIM, Nondata MVNO SIM.
1263         for (SubscriptionInfo subInfo : infos) {
1264             int subId = subInfo.getSubscriptionId();
1265             if (requiresImsiEncryption(subId) && !isImsiEncryptionInfoAvailable(subId)) {
1266                 vlogd("required IMSI encryption information is not available.");
1267                 continue;
1268             }
1269             TelephonyManager specifiedTm = mTelephonyManager.createForSubscriptionId(subId);
1270             String operatorNumeric = specifiedTm.getSimOperator();
1271             if (operatorNumeric != null && imsiParameter.matchesMccMnc(operatorNumeric)) {
1272                 String curImsi = specifiedTm.getSubscriberId();
1273                 if (TextUtils.isEmpty(curImsi)) {
1274                     continue;
1275                 }
1276                 matchedPair = new Pair<>(curImsi, subInfo.getCarrierId());
1277                 if (subId == dataSubId) {
1278                     matchedDataPair = matchedPair;
1279                     if (getCarrierType(subId) == CARRIER_MNO_TYPE) {
1280                         vlogd("MNO data is matched via IMSI.");
1281                         return matchedDataPair;
1282                     }
1283                 }
1284                 if (getCarrierType(subId) == CARRIER_MNO_TYPE) {
1285                     matchedMnoPair = matchedPair;
1286                 }
1287             }
1288         }
1289 
1290         if (matchedMnoPair != null) {
1291             vlogd("MNO sub is matched via IMSI.");
1292             return matchedMnoPair;
1293         }
1294 
1295         if (matchedDataPair != null) {
1296             vlogd("MVNO data sub is matched via IMSI.");
1297             return matchedDataPair;
1298         }
1299 
1300         return matchedPair;
1301     }
1302 
vlogd(String msg)1303     private void vlogd(String msg) {
1304         if (!mVerboseLogEnabled) {
1305             return;
1306         }
1307 
1308         Log.d(TAG, msg);
1309     }
1310 
1311     /** Dump state. */
dump(FileDescriptor fd, PrintWriter pw, String[] args)1312     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
1313         pw.println(TAG + ": ");
1314         pw.println("mImsiEncryptionRequired=" + mImsiEncryptionRequired);
1315         pw.println("mImsiEncryptionInfoAvailable=" + mImsiEncryptionInfoAvailable);
1316     }
1317 
1318     /**
1319      * Get the carrier ID {@link TelephonyManager#getSimCarrierId()} of the carrier which give
1320      * target package carrier privileges.
1321      *
1322      * @param packageName target package to check if grant privileges by any carrier.
1323      * @return Carrier ID who give privilege to this package. If package isn't granted privilege
1324      *         by any available carrier, will return UNKNOWN_CARRIER_ID.
1325      */
getCarrierIdForPackageWithCarrierPrivileges(String packageName)1326     public int getCarrierIdForPackageWithCarrierPrivileges(String packageName) {
1327         List<SubscriptionInfo> subInfoList = mSubscriptionManager.getActiveSubscriptionInfoList();
1328         if (subInfoList == null || subInfoList.isEmpty()) {
1329             if (mVerboseLogEnabled) Log.v(TAG, "No subs for carrier privilege check");
1330             return TelephonyManager.UNKNOWN_CARRIER_ID;
1331         }
1332         for (SubscriptionInfo info : subInfoList) {
1333             TelephonyManager specifiedTm =
1334                     mTelephonyManager.createForSubscriptionId(info.getSubscriptionId());
1335             if (specifiedTm.checkCarrierPrivilegesForPackage(packageName)
1336                     == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
1337                 return info.getCarrierId();
1338             }
1339         }
1340         return TelephonyManager.UNKNOWN_CARRIER_ID;
1341     }
1342 
1343     /**
1344      * Get the carrier name for target subscription id.
1345      * @param subId Subscription id
1346      * @return String of carrier name.
1347      */
getCarrierNameforSubId(int subId)1348     public String getCarrierNameforSubId(int subId) {
1349         TelephonyManager specifiedTm =
1350                 mTelephonyManager.createForSubscriptionId(subId);
1351 
1352         CharSequence name = specifiedTm.getSimCarrierIdName();
1353         if (name == null) {
1354             return null;
1355         }
1356         return name.toString();
1357     }
1358 
1359     /**
1360      * Check if a config is carrier network and from the non default data SIM.
1361      * @return True if it is carrier network and from non default data SIM,otherwise return false.
1362      */
isCarrierNetworkFromNonDefaultDataSim(WifiConfiguration config)1363     public boolean isCarrierNetworkFromNonDefaultDataSim(WifiConfiguration config) {
1364         if (config.carrierId == TelephonyManager.UNKNOWN_CARRIER_ID) {
1365             return false;
1366         }
1367         int subId = getMatchingSubId(config.carrierId);
1368         return subId != SubscriptionManager.getDefaultDataSubscriptionId();
1369     }
1370 
1371     /**
1372      * Get the carrier Id of the default data sim.
1373      */
getDefaultDataSimCarrierId()1374     public int getDefaultDataSimCarrierId() {
1375         int subId = SubscriptionManager.getDefaultDataSubscriptionId();
1376         TelephonyManager specifiedTm = mTelephonyManager.createForSubscriptionId(subId);
1377         return specifiedTm.getSimCarrierId();
1378     }
1379 
1380     /**
1381      * Add a listener to monitor user approval IMSI protection exemption.
1382      */
addImsiExemptionUserApprovalListener( OnUserApproveCarrierListener listener)1383     public void addImsiExemptionUserApprovalListener(
1384             OnUserApproveCarrierListener listener) {
1385         mOnUserApproveCarrierListeners.add(listener);
1386     }
1387 
1388     /**
1389      * Clear the Imsi Privacy Exemption user approval info the target carrier.
1390      */
clearImsiPrivacyExemptionForCarrier(int carrierId)1391     public void clearImsiPrivacyExemptionForCarrier(int carrierId) {
1392         mImsiPrivacyProtectionExemptionMap.remove(carrierId);
1393         saveToStore();
1394     }
1395 
1396     /**
1397      * Check if carrier have user approved exemption for IMSI protection
1398      */
hasUserApprovedImsiPrivacyExemptionForCarrier(int carrierId)1399     public boolean hasUserApprovedImsiPrivacyExemptionForCarrier(int carrierId) {
1400         return  mImsiPrivacyProtectionExemptionMap.getOrDefault(carrierId, false);
1401     }
1402 
1403     /**
1404      * Enable or disable exemption on IMSI protection.
1405      */
setHasUserApprovedImsiPrivacyExemptionForCarrier(boolean approved, int carrierId)1406     public void setHasUserApprovedImsiPrivacyExemptionForCarrier(boolean approved, int carrierId) {
1407         if (mVerboseLogEnabled) {
1408             Log.v(TAG, "Setting Imsi privacy exemption for carrier " + carrierId
1409                     + (approved ? " approved" : " not approved"));
1410         }
1411         mImsiPrivacyProtectionExemptionMap.put(carrierId, approved);
1412         // If user approved the exemption restore to initial auto join configure.
1413         if (approved) {
1414             for (OnUserApproveCarrierListener listener : mOnUserApproveCarrierListeners) {
1415                 listener.onUserAllowed(carrierId);
1416             }
1417         }
1418         saveToStore();
1419     }
1420 
sendImsiPrivacyNotification(int carrierId)1421     private void sendImsiPrivacyNotification(int carrierId) {
1422         String carrierName = getCarrierNameforSubId(getMatchingSubId(carrierId));
1423         Notification.Action userAllowAppNotificationAction =
1424                 new Notification.Action.Builder(null,
1425                         mResources.getText(R.string
1426                                 .wifi_suggestion_action_allow_imsi_privacy_exemption_carrier),
1427                         getPrivateBroadcast(NOTIFICATION_USER_ALLOWED_CARRIER_INTENT_ACTION,
1428                                 Pair.create(EXTRA_CARRIER_NAME, carrierName),
1429                                 Pair.create(EXTRA_CARRIER_ID, carrierId)))
1430                         .build();
1431         Notification.Action userDisallowAppNotificationAction =
1432                 new Notification.Action.Builder(null,
1433                         mResources.getText(R.string
1434                                 .wifi_suggestion_action_disallow_imsi_privacy_exemption_carrier),
1435                         getPrivateBroadcast(NOTIFICATION_USER_DISALLOWED_CARRIER_INTENT_ACTION,
1436                                 Pair.create(EXTRA_CARRIER_NAME, carrierName),
1437                                 Pair.create(EXTRA_CARRIER_ID, carrierId)))
1438                         .build();
1439 
1440         Notification notification = mFrameworkFacade.makeNotificationBuilder(
1441                 mContext, WifiService.NOTIFICATION_NETWORK_STATUS)
1442                 .setSmallIcon(Icon.createWithResource(mContext.getWifiOverlayApkPkgName(),
1443                         com.android.wifi.resources.R.drawable.stat_notify_wifi_in_range))
1444                 .setTicker(mResources.getString(
1445                         R.string.wifi_suggestion_imsi_privacy_title, carrierName))
1446                 .setContentTitle(mResources.getString(
1447                         R.string.wifi_suggestion_imsi_privacy_title, carrierName))
1448                 .setStyle(new Notification.BigTextStyle()
1449                         .bigText(mResources.getString(
1450                                 R.string.wifi_suggestion_imsi_privacy_content)))
1451                 .setDeleteIntent(getPrivateBroadcast(NOTIFICATION_USER_DISMISSED_INTENT_ACTION,
1452                         Pair.create(EXTRA_CARRIER_NAME, carrierName),
1453                         Pair.create(EXTRA_CARRIER_ID, carrierId)))
1454                 .setShowWhen(false)
1455                 .setLocalOnly(true)
1456                 .setColor(mResources.getColor(android.R.color.system_notification_accent_color,
1457                         mContext.getTheme()))
1458                 .addAction(userDisallowAppNotificationAction)
1459                 .addAction(userAllowAppNotificationAction)
1460                 .build();
1461 
1462         // Post the notification.
1463         mNotificationManager.notify(
1464                 SystemMessageProto.SystemMessage.NOTE_NETWORK_SUGGESTION_AVAILABLE, notification);
1465         mUserApprovalUiActive = true;
1466         mIsLastUserApprovalUiDialog = false;
1467     }
1468 
sendImsiPrivacyConfirmationDialog(@onNull String carrierName, int carrierId)1469     private void sendImsiPrivacyConfirmationDialog(@NonNull String carrierName, int carrierId) {
1470         mWifiMetrics.addUserApprovalCarrierUiReaction(ACTION_USER_ALLOWED_CARRIER,
1471                 mIsLastUserApprovalUiDialog);
1472         AlertDialog dialog = mFrameworkFacade.makeAlertDialogBuilder(mContext)
1473                 .setTitle(mResources.getString(
1474                         R.string.wifi_suggestion_imsi_privacy_exemption_confirmation_title))
1475                 .setMessage(mResources.getString(
1476                         R.string.wifi_suggestion_imsi_privacy_exemption_confirmation_content,
1477                         carrierName))
1478                 .setPositiveButton(mResources.getText(
1479                         R.string.wifi_suggestion_action_allow_imsi_privacy_exemption_confirmation),
1480                         (d, which) -> mHandler.post(
1481                                 () -> handleUserAllowCarrierExemptionAction(
1482                                         carrierName, carrierId)))
1483                 .setNegativeButton(mResources.getText(
1484                         R.string.wifi_suggestion_action_disallow_imsi_privacy_exemption_confirmation),
1485                         (d, which) -> mHandler.post(
1486                                 () -> handleUserDisallowCarrierExemptionAction(
1487                                         carrierName, carrierId)))
1488                 .setOnDismissListener(
1489                         (d) -> mHandler.post(this::handleUserDismissAction))
1490                 .setOnCancelListener(
1491                         (d) -> mHandler.post(this::handleUserDismissAction))
1492                 .create();
1493         dialog.setCanceledOnTouchOutside(false);
1494         dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
1495         dialog.getWindow().addSystemFlags(
1496                 WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS);
1497         dialog.show();
1498         mUserApprovalUiActive = true;
1499         mIsLastUserApprovalUiDialog = true;
1500     }
1501 
1502     /**
1503      * Send notification for exemption of IMSI protection if user never made choice before.
1504      */
sendImsiProtectionExemptionNotificationIfRequired(int carrierId)1505     public void sendImsiProtectionExemptionNotificationIfRequired(int carrierId) {
1506         int subId = getMatchingSubId(carrierId);
1507         // If user data isn't loaded, don't send notification.
1508         if (!mUserDataLoaded) {
1509             return;
1510         }
1511         if (requiresImsiEncryption(subId)) {
1512             return;
1513         }
1514         if (mImsiPrivacyProtectionExemptionMap.containsKey(carrierId)) {
1515             return;
1516         }
1517         if (mUserApprovalUiActive) {
1518             return;
1519         }
1520         Log.i(TAG, "Sending IMSI protection notification for " + carrierId);
1521         sendImsiPrivacyNotification(carrierId);
1522     }
1523 
getPrivateBroadcast(@onNull String action, @NonNull Pair<String, String> extra1, @NonNull Pair<String, Integer> extra2)1524     private PendingIntent getPrivateBroadcast(@NonNull String action,
1525             @NonNull Pair<String, String> extra1, @NonNull Pair<String, Integer> extra2) {
1526         Intent intent = new Intent(action)
1527                 .setPackage(mWifiInjector.getWifiStackPackageName())
1528                 .putExtra(extra1.first, extra1.second)
1529                 .putExtra(extra2.first, extra2.second);
1530         return mFrameworkFacade.getBroadcast(mContext, 0, intent,
1531                 PendingIntent.FLAG_UPDATE_CURRENT);
1532     }
1533 
saveToStore()1534     private void saveToStore() {
1535         // Set the flag to let WifiConfigStore that we have new data to write.
1536         mHasNewDataToSerialize = true;
1537         if (!mWifiInjector.getWifiConfigManager().saveToStore(true)) {
1538             Log.w(TAG, "Failed to save to store");
1539         }
1540     }
1541 
1542     /**
1543      * Helper method for user factory reset network setting.
1544      */
clear()1545     public void clear() {
1546         mImsiPrivacyProtectionExemptionMap.clear();
1547         saveToStore();
1548     }
1549 }
1550