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