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