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