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.hotspot2; 18 19 import static android.app.AppOpsManager.OPSTR_CHANGE_WIFI_STATE; 20 import static android.net.wifi.WifiConfiguration.MeteredOverride; 21 import static android.net.wifi.WifiInfo.DEFAULT_MAC_ADDRESS; 22 23 import static java.security.cert.PKIXReason.NO_TRUST_ANCHOR; 24 25 import android.annotation.NonNull; 26 import android.annotation.Nullable; 27 import android.app.AppOpsManager; 28 import android.content.Context; 29 import android.net.MacAddress; 30 import android.net.wifi.ScanResult; 31 import android.net.wifi.WifiConfiguration; 32 import android.net.wifi.WifiEnterpriseConfig; 33 import android.net.wifi.WifiManager; 34 import android.net.wifi.WifiSsid; 35 import android.net.wifi.hotspot2.IProvisioningCallback; 36 import android.net.wifi.hotspot2.OsuProvider; 37 import android.net.wifi.hotspot2.PasspointConfiguration; 38 import android.os.Handler; 39 import android.os.Looper; 40 import android.os.Process; 41 import android.text.TextUtils; 42 import android.util.Log; 43 import android.util.Pair; 44 45 import com.android.internal.annotations.VisibleForTesting; 46 import com.android.modules.utils.build.SdkLevel; 47 import com.android.server.wifi.Clock; 48 import com.android.server.wifi.MacAddressUtil; 49 import com.android.server.wifi.NetworkUpdateResult; 50 import com.android.server.wifi.WifiCarrierInfoManager; 51 import com.android.server.wifi.WifiConfigManager; 52 import com.android.server.wifi.WifiConfigStore; 53 import com.android.server.wifi.WifiInjector; 54 import com.android.server.wifi.WifiKeyStore; 55 import com.android.server.wifi.WifiMetrics; 56 import com.android.server.wifi.WifiNative; 57 import com.android.server.wifi.hotspot2.anqp.ANQPElement; 58 import com.android.server.wifi.hotspot2.anqp.Constants; 59 import com.android.server.wifi.hotspot2.anqp.HSOsuProvidersElement; 60 import com.android.server.wifi.hotspot2.anqp.I18Name; 61 import com.android.server.wifi.hotspot2.anqp.OsuProviderInfo; 62 import com.android.server.wifi.hotspot2.anqp.VenueNameElement; 63 import com.android.server.wifi.hotspot2.anqp.VenueUrlElement; 64 import com.android.server.wifi.proto.nano.WifiMetricsProto.UserActionEvent; 65 import com.android.server.wifi.util.InformationElementUtil; 66 import com.android.server.wifi.util.WifiPermissionsUtil; 67 68 import java.io.IOException; 69 import java.io.PrintWriter; 70 import java.net.URL; 71 import java.security.GeneralSecurityException; 72 import java.security.KeyStore; 73 import java.security.cert.CertPath; 74 import java.security.cert.CertPathValidator; 75 import java.security.cert.CertPathValidatorException; 76 import java.security.cert.CertificateFactory; 77 import java.security.cert.PKIXParameters; 78 import java.security.cert.X509Certificate; 79 import java.util.ArrayList; 80 import java.util.Arrays; 81 import java.util.Collections; 82 import java.util.HashMap; 83 import java.util.HashSet; 84 import java.util.List; 85 import java.util.Locale; 86 import java.util.Map; 87 import java.util.Set; 88 import java.util.stream.Collectors; 89 90 /** 91 * This class provides the APIs to manage Passpoint provider configurations. 92 * It deals with the following: 93 * - Maintaining a list of configured Passpoint providers for provider matching. 94 * - Persisting the providers configurations to store when required. 95 * - matching Passpoint providers based on the scan results 96 * - Supporting WifiManager Public API calls: 97 * > addOrUpdatePasspointConfiguration() 98 * > removePasspointConfiguration() 99 * > getPasspointConfigurations() 100 * 101 * The provider matching requires obtaining additional information from the AP (ANQP elements). 102 * The ANQP elements will be cached using {@link AnqpCache} to avoid unnecessary requests. 103 * 104 * NOTE: These API's are not thread safe and should only be used from the main Wifi thread. 105 */ 106 public class PasspointManager { 107 private static final String TAG = "PasspointManager"; 108 109 /** 110 * Handle for the current {@link PasspointManager} instance. This is needed to avoid 111 * circular dependency with the WifiConfigManger, it will be used for adding the 112 * legacy Passpoint configurations. 113 * 114 * This can be eliminated once we can remove the dependency for WifiConfigManager (for 115 * triggering config store write) from this class. 116 */ 117 private static PasspointManager sPasspointManager; 118 119 private final PasspointEventHandler mPasspointEventHandler; 120 private final WifiInjector mWifiInjector; 121 private final Handler mHandler; 122 private final WifiKeyStore mKeyStore; 123 private final PasspointObjectFactory mObjectFactory; 124 125 private final Map<String, PasspointProvider> mProviders; 126 private final AnqpCache mAnqpCache; 127 private final ANQPRequestManager mAnqpRequestManager; 128 private final WifiConfigManager mWifiConfigManager; 129 private final WifiMetrics mWifiMetrics; 130 private final PasspointProvisioner mPasspointProvisioner; 131 private final AppOpsManager mAppOps; 132 private final WifiCarrierInfoManager mWifiCarrierInfoManager; 133 private final MacAddressUtil mMacAddressUtil; 134 private final Clock mClock; 135 private final WifiPermissionsUtil mWifiPermissionsUtil; 136 137 /** 138 * Map of package name of an app to the app ops changed listener for the app. 139 */ 140 private final Map<String, AppOpsChangedListener> mAppOpsChangedListenerPerApp = new HashMap<>(); 141 142 // Counter used for assigning unique identifier to each provider. 143 private long mProviderIndex; 144 private boolean mVerboseLoggingEnabled = false; 145 146 private class CallbackHandler implements PasspointEventHandler.Callbacks { 147 private final Context mContext; CallbackHandler(Context context)148 CallbackHandler(Context context) { 149 mContext = context; 150 } 151 152 @Override onANQPResponse(long bssid, Map<Constants.ANQPElementType, ANQPElement> anqpElements)153 public void onANQPResponse(long bssid, 154 Map<Constants.ANQPElementType, ANQPElement> anqpElements) { 155 if (mVerboseLoggingEnabled) { 156 Log.d(TAG, "ANQP response received from BSSID " 157 + Utils.macToString(bssid) + " - List of ANQP elements:"); 158 int i = 0; 159 for (Constants.ANQPElementType type : anqpElements.keySet()) { 160 Log.d(TAG, "#" + i++ + ": " + type); 161 } 162 } 163 // Notify request manager for the completion of a request. 164 ANQPNetworkKey anqpKey = 165 mAnqpRequestManager.onRequestCompleted(bssid, anqpElements != null); 166 if (anqpElements == null || anqpKey == null) { 167 // Query failed or the request wasn't originated from us (not tracked by the 168 // request manager). Nothing to be done. 169 return; 170 } 171 172 if (anqpElements.containsKey(Constants.ANQPElementType.ANQPVenueUrl)) { 173 // Venue URL ANQP is requested and received only after the network is connected 174 mWifiMetrics.incrementTotalNumberOfPasspointConnectionsWithVenueUrl(); 175 } 176 177 // Add new entry to the cache. 178 mAnqpCache.addOrUpdateEntry(anqpKey, anqpElements); 179 } 180 181 @Override onIconResponse(long bssid, String fileName, byte[] data)182 public void onIconResponse(long bssid, String fileName, byte[] data) { 183 // Empty 184 } 185 186 @Override onWnmFrameReceived(WnmData event)187 public void onWnmFrameReceived(WnmData event) { 188 // Empty 189 } 190 } 191 192 /** 193 * Data provider for the Passpoint configuration store data 194 * {@link PasspointConfigUserStoreData}. 195 */ 196 private class UserDataSourceHandler implements PasspointConfigUserStoreData.DataSource { 197 @Override getProviders()198 public List<PasspointProvider> getProviders() { 199 List<PasspointProvider> providers = new ArrayList<>(); 200 for (Map.Entry<String, PasspointProvider> entry : mProviders.entrySet()) { 201 providers.add(entry.getValue()); 202 } 203 return providers; 204 } 205 206 @Override setProviders(List<PasspointProvider> providers)207 public void setProviders(List<PasspointProvider> providers) { 208 mProviders.clear(); 209 for (PasspointProvider provider : providers) { 210 provider.enableVerboseLogging(mVerboseLoggingEnabled); 211 mProviders.put(provider.getConfig().getUniqueId(), provider); 212 if (provider.getPackageName() != null) { 213 startTrackingAppOpsChange(provider.getPackageName(), 214 provider.getCreatorUid()); 215 } 216 } 217 } 218 } 219 220 /** 221 * Data provider for the Passpoint configuration store data 222 * {@link PasspointConfigSharedStoreData}. 223 */ 224 private class SharedDataSourceHandler implements PasspointConfigSharedStoreData.DataSource { 225 @Override getProviderIndex()226 public long getProviderIndex() { 227 return mProviderIndex; 228 } 229 230 @Override setProviderIndex(long providerIndex)231 public void setProviderIndex(long providerIndex) { 232 mProviderIndex = providerIndex; 233 } 234 } 235 236 /** 237 * Listener for app-ops changes for apps to remove the corresponding Passpoint profiles. 238 */ 239 private final class AppOpsChangedListener implements AppOpsManager.OnOpChangedListener { 240 private final String mPackageName; 241 private final int mUid; 242 AppOpsChangedListener(@onNull String packageName, int uid)243 AppOpsChangedListener(@NonNull String packageName, int uid) { 244 mPackageName = packageName; 245 mUid = uid; 246 } 247 248 @Override onOpChanged(String op, String packageName)249 public void onOpChanged(String op, String packageName) { 250 mHandler.post(() -> { 251 if (!mPackageName.equals(packageName)) return; 252 if (!OPSTR_CHANGE_WIFI_STATE.equals(op)) return; 253 254 // Ensures the uid to package mapping is still correct. 255 try { 256 mAppOps.checkPackage(mUid, mPackageName); 257 } catch (SecurityException e) { 258 Log.wtf(TAG, "Invalid uid/package" + packageName); 259 return; 260 } 261 if (mAppOps.unsafeCheckOpNoThrow(OPSTR_CHANGE_WIFI_STATE, mUid, mPackageName) 262 == AppOpsManager.MODE_IGNORED) { 263 Log.i(TAG, "User disallowed change wifi state for " + packageName); 264 265 // Removes the profiles installed by the app from database. 266 removePasspointProviderWithPackage(mPackageName); 267 } 268 }); 269 } 270 } 271 272 private class OnNetworkUpdateListener implements 273 WifiConfigManager.OnNetworkUpdateListener { 274 @Override onConnectChoiceSet(@onNull List<WifiConfiguration> networks, String choiceKey, int rssi)275 public void onConnectChoiceSet(@NonNull List<WifiConfiguration> networks, 276 String choiceKey, int rssi) { 277 onUserConnectChoiceSet(networks, choiceKey, rssi); 278 } 279 @Override onConnectChoiceRemoved(String choiceKey)280 public void onConnectChoiceRemoved(String choiceKey) { 281 onUserConnectChoiceRemove(choiceKey); 282 } 283 284 } 285 onUserConnectChoiceRemove(String choiceKey)286 private void onUserConnectChoiceRemove(String choiceKey) { 287 mProviders.values().stream() 288 .filter(provider -> TextUtils.equals(provider.getConnectChoice(), choiceKey)) 289 .forEach(provider -> { 290 provider.setUserConnectChoice(null, 0); 291 }); 292 mWifiConfigManager.saveToStore(true); 293 } 294 onUserConnectChoiceSet(List<WifiConfiguration> networks, String choiceKey, int rssi)295 private void onUserConnectChoiceSet(List<WifiConfiguration> networks, String choiceKey, 296 int rssi) { 297 for (WifiConfiguration config : networks) { 298 PasspointProvider provider = mProviders.get(config.getProfileKey()); 299 if (provider != null) { 300 provider.setUserConnectChoice(choiceKey, rssi); 301 } 302 } 303 PasspointProvider provider = mProviders.get(choiceKey); 304 if (provider != null) { 305 provider.setUserConnectChoice(null, 0); 306 } 307 mWifiConfigManager.saveToStore(true); 308 } 309 310 /** 311 * Remove all Passpoint profiles installed by the app that has been disabled or uninstalled. 312 * 313 * @param packageName Package name of the app to remove the corresponding Passpoint profiles. 314 */ removePasspointProviderWithPackage(@onNull String packageName)315 public void removePasspointProviderWithPackage(@NonNull String packageName) { 316 stopTrackingAppOpsChange(packageName); 317 for (Map.Entry<String, PasspointProvider> entry : getPasspointProviderWithPackage( 318 packageName).entrySet()) { 319 String uniqueId = entry.getValue().getConfig().getUniqueId(); 320 removeProvider(Process.WIFI_UID /* ignored */, true, uniqueId, null); 321 } 322 } 323 getPasspointProviderWithPackage( @onNull String packageName)324 private Map<String, PasspointProvider> getPasspointProviderWithPackage( 325 @NonNull String packageName) { 326 return mProviders.entrySet().stream().filter( 327 entry -> TextUtils.equals(packageName, 328 entry.getValue().getPackageName())).collect( 329 Collectors.toMap(entry -> entry.getKey(), entry -> entry.getValue())); 330 } 331 startTrackingAppOpsChange(@onNull String packageName, int uid)332 private void startTrackingAppOpsChange(@NonNull String packageName, int uid) { 333 // The package is already registered. 334 if (mAppOpsChangedListenerPerApp.containsKey(packageName)) return; 335 AppOpsChangedListener appOpsChangedListener = new AppOpsChangedListener(packageName, uid); 336 mAppOps.startWatchingMode(OPSTR_CHANGE_WIFI_STATE, packageName, appOpsChangedListener); 337 mAppOpsChangedListenerPerApp.put(packageName, appOpsChangedListener); 338 } 339 stopTrackingAppOpsChange(@onNull String packageName)340 private void stopTrackingAppOpsChange(@NonNull String packageName) { 341 AppOpsChangedListener appOpsChangedListener = mAppOpsChangedListenerPerApp.remove( 342 packageName); 343 if (appOpsChangedListener == null) { 344 Log.i(TAG, "No app ops listener found for " + packageName); 345 return; 346 } 347 mAppOps.stopWatchingMode(appOpsChangedListener); 348 } 349 PasspointManager(Context context, WifiInjector wifiInjector, Handler handler, WifiNative wifiNative, WifiKeyStore keyStore, Clock clock, PasspointObjectFactory objectFactory, WifiConfigManager wifiConfigManager, WifiConfigStore wifiConfigStore, WifiMetrics wifiMetrics, WifiCarrierInfoManager wifiCarrierInfoManager, MacAddressUtil macAddressUtil, WifiPermissionsUtil wifiPermissionsUtil)350 public PasspointManager(Context context, WifiInjector wifiInjector, Handler handler, 351 WifiNative wifiNative, WifiKeyStore keyStore, Clock clock, 352 PasspointObjectFactory objectFactory, WifiConfigManager wifiConfigManager, 353 WifiConfigStore wifiConfigStore, 354 WifiMetrics wifiMetrics, 355 WifiCarrierInfoManager wifiCarrierInfoManager, 356 MacAddressUtil macAddressUtil, 357 WifiPermissionsUtil wifiPermissionsUtil) { 358 mPasspointEventHandler = objectFactory.makePasspointEventHandler(wifiInjector, 359 new CallbackHandler(context)); 360 mWifiInjector = wifiInjector; 361 mHandler = handler; 362 mKeyStore = keyStore; 363 mObjectFactory = objectFactory; 364 mProviders = new HashMap<>(); 365 mAnqpCache = objectFactory.makeAnqpCache(clock); 366 mAnqpRequestManager = objectFactory.makeANQPRequestManager(mPasspointEventHandler, clock); 367 mWifiConfigManager = wifiConfigManager; 368 mWifiMetrics = wifiMetrics; 369 mProviderIndex = 0; 370 mWifiCarrierInfoManager = wifiCarrierInfoManager; 371 wifiConfigStore.registerStoreData(objectFactory.makePasspointConfigUserStoreData( 372 mKeyStore, mWifiCarrierInfoManager, new UserDataSourceHandler(), clock)); 373 wifiConfigStore.registerStoreData(objectFactory.makePasspointConfigSharedStoreData( 374 new SharedDataSourceHandler())); 375 mPasspointProvisioner = objectFactory.makePasspointProvisioner(context, wifiNative, 376 this, wifiMetrics); 377 mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); 378 sPasspointManager = this; 379 mMacAddressUtil = macAddressUtil; 380 mClock = clock; 381 mWifiConfigManager.addOnNetworkUpdateListener( 382 new PasspointManager.OnNetworkUpdateListener()); 383 mWifiPermissionsUtil = wifiPermissionsUtil; 384 } 385 386 /** 387 * Initializes the provisioning flow with a looper. 388 * This looper should be tied to a background worker thread since PasspointProvisioner has a 389 * heavy workload. 390 */ initializeProvisioner(Looper looper)391 public void initializeProvisioner(Looper looper) { 392 mPasspointProvisioner.init(looper); 393 } 394 395 /** 396 * Enable verbose logging 397 * @param verbose enables verbose logging 398 */ enableVerboseLogging(boolean verbose)399 public void enableVerboseLogging(boolean verbose) { 400 mVerboseLoggingEnabled = verbose; 401 mPasspointProvisioner.enableVerboseLogging(verbose); 402 for (PasspointProvider provider : mProviders.values()) { 403 provider.enableVerboseLogging(verbose); 404 } 405 } 406 updateWifiConfigInWcmIfPresent( WifiConfiguration newConfig, int uid, String packageName, boolean isFromSuggestion)407 private void updateWifiConfigInWcmIfPresent( 408 WifiConfiguration newConfig, int uid, String packageName, boolean isFromSuggestion) { 409 WifiConfiguration configInWcm = 410 mWifiConfigManager.getConfiguredNetwork(newConfig.getProfileKey()); 411 if (configInWcm == null) return; 412 // suggestion != saved 413 if (isFromSuggestion != configInWcm.fromWifiNetworkSuggestion) return; 414 // is suggestion from same app. 415 if (isFromSuggestion 416 && (configInWcm.creatorUid != uid 417 || !TextUtils.equals(configInWcm.creatorName, packageName))) { 418 return; 419 } 420 NetworkUpdateResult result = mWifiConfigManager.addOrUpdateNetwork( 421 newConfig, uid, packageName); 422 if (!result.isSuccess()) { 423 Log.e(TAG, "Failed to update config in WifiConfigManager"); 424 } else { 425 mWifiConfigManager.allowAutojoin(result.getNetworkId(), newConfig.allowAutojoin); 426 if (mVerboseLoggingEnabled) { 427 Log.v(TAG, "Updated config in WifiConfigManager"); 428 } 429 } 430 } 431 432 /** 433 * Add or update a Passpoint provider with the given configuration. 434 * 435 * Each provider is uniquely identified by its unique identifier, see 436 * {@link PasspointConfiguration#getUniqueId()}. 437 * In the case when there is an existing configuration with the same unique identifier, 438 * a provider with the new configuration will replace the existing provider. 439 * 440 * @param config Configuration of the Passpoint provider to be added 441 * @param uid Uid of the app adding/Updating {@code config} 442 * @param packageName Package name of the app adding/Updating {@code config} 443 * @param isFromSuggestion Whether this {@code config} is from suggestion API 444 * @param isTrusted Whether this {@code config} an trusted network, default should be true. 445 * Only able set to false when {@code isFromSuggestion} is true, otherwise 446 * adding {@code config} will be false. 447 * @return true if provider is added, false otherwise 448 */ addOrUpdateProvider(PasspointConfiguration config, int uid, String packageName, boolean isFromSuggestion, boolean isTrusted)449 public boolean addOrUpdateProvider(PasspointConfiguration config, int uid, 450 String packageName, boolean isFromSuggestion, boolean isTrusted) { 451 mWifiMetrics.incrementNumPasspointProviderInstallation(); 452 if (config == null) { 453 Log.e(TAG, "Configuration not provided"); 454 return false; 455 } 456 if (!config.validate()) { 457 Log.e(TAG, "Invalid configuration"); 458 return false; 459 } 460 if (!(isFromSuggestion || isTrusted)) { 461 Log.e(TAG, "Set isTrusted to false on a non suggestion passpoint is not allowed"); 462 return false; 463 } 464 if (!mWifiPermissionsUtil.doesUidBelongToCurrentUser(uid)) { 465 Log.e(TAG, "UID " + uid + " not visible to the current user"); 466 return false; 467 } 468 469 mWifiCarrierInfoManager.tryUpdateCarrierIdForPasspoint(config); 470 // Create a provider and install the necessary certificates and keys. 471 PasspointProvider newProvider = mObjectFactory.makePasspointProvider(config, mKeyStore, 472 mWifiCarrierInfoManager, mProviderIndex++, uid, packageName, isFromSuggestion, 473 mClock); 474 newProvider.setTrusted(isTrusted); 475 476 boolean metricsNoRootCa = false; 477 boolean metricsSelfSignedRootCa = false; 478 boolean metricsSubscriptionExpiration = false; 479 480 if (config.getCredential().getUserCredential() != null 481 || config.getCredential().getCertCredential() != null) { 482 X509Certificate[] x509Certificates = config.getCredential().getCaCertificates(); 483 if (x509Certificates == null) { 484 metricsNoRootCa = true; 485 } else { 486 try { 487 for (X509Certificate certificate : x509Certificates) { 488 verifyCaCert(certificate); 489 } 490 } catch (CertPathValidatorException e) { 491 // A self signed Root CA will fail path validation checks with NO_TRUST_ANCHOR 492 if (e.getReason() == NO_TRUST_ANCHOR) { 493 metricsSelfSignedRootCa = true; 494 } 495 } catch (Exception e) { 496 // Other exceptions, fall through, will be handled below 497 } 498 } 499 } 500 if (config.getSubscriptionExpirationTimeMillis() != Long.MIN_VALUE) { 501 metricsSubscriptionExpiration = true; 502 } 503 504 if (!newProvider.installCertsAndKeys()) { 505 Log.e(TAG, "Failed to install certificates and keys to keystore"); 506 return false; 507 } 508 509 // Remove existing provider with the same unique ID. 510 if (mProviders.containsKey(config.getUniqueId())) { 511 PasspointProvider old = mProviders.get(config.getUniqueId()); 512 // If new profile is from suggestion and from a different App, ignore new profile, 513 // return false. 514 // If from same app, update it. 515 if (isFromSuggestion && !old.getPackageName().equals(packageName)) { 516 newProvider.uninstallCertsAndKeys(); 517 return false; 518 } 519 Log.d(TAG, "Replacing configuration for FQDN: " + config.getHomeSp().getFqdn() 520 + " and unique ID: " + config.getUniqueId()); 521 old.uninstallCertsAndKeys(); 522 mProviders.remove(config.getUniqueId()); 523 // Keep the user connect choice and AnonymousIdentity 524 newProvider.setUserConnectChoice(old.getConnectChoice(), old.getConnectChoiceRssi()); 525 newProvider.setAnonymousIdentity(old.getAnonymousIdentity()); 526 // New profile changes the credential, remove the related WifiConfig. 527 if (!old.equals(newProvider)) { 528 mWifiConfigManager.removePasspointConfiguredNetwork( 529 newProvider.getWifiConfig().getProfileKey()); 530 } else { 531 // If there is a config cached in WifiConfigManager, update it with new info. 532 updateWifiConfigInWcmIfPresent( 533 newProvider.getWifiConfig(), uid, packageName, isFromSuggestion); 534 } 535 } 536 newProvider.enableVerboseLogging(mVerboseLoggingEnabled); 537 mProviders.put(config.getUniqueId(), newProvider); 538 mWifiConfigManager.saveToStore(true /* forceWrite */); 539 if (!isFromSuggestion && newProvider.getPackageName() != null) { 540 startTrackingAppOpsChange(newProvider.getPackageName(), uid); 541 } 542 Log.d(TAG, "Added/updated Passpoint configuration for FQDN: " 543 + config.getHomeSp().getFqdn() + " with unique ID: " + config.getUniqueId() 544 + " by UID: " + uid); 545 if (metricsNoRootCa) { 546 mWifiMetrics.incrementNumPasspointProviderWithNoRootCa(); 547 } 548 if (metricsSelfSignedRootCa) { 549 mWifiMetrics.incrementNumPasspointProviderWithSelfSignedRootCa(); 550 } 551 if (metricsSubscriptionExpiration) { 552 mWifiMetrics.incrementNumPasspointProviderWithSubscriptionExpiration(); 553 } 554 if (SdkLevel.isAtLeastS() && config.getDecoratedIdentityPrefix() != null) { 555 mWifiMetrics.incrementTotalNumberOfPasspointProfilesWithDecoratedIdentity(); 556 } 557 mWifiMetrics.incrementNumPasspointProviderInstallSuccess(); 558 return true; 559 } 560 removeProviderInternal(PasspointProvider provider, int callingUid, boolean privileged)561 private boolean removeProviderInternal(PasspointProvider provider, int callingUid, 562 boolean privileged) { 563 if (!privileged && callingUid != provider.getCreatorUid()) { 564 Log.e(TAG, "UID " + callingUid + " cannot remove profile created by " 565 + provider.getCreatorUid()); 566 return false; 567 } 568 if (!mWifiPermissionsUtil.doesUidBelongToCurrentUser(callingUid)) { 569 Log.e(TAG, "UID " + callingUid + " not visible to the current user"); 570 return false; 571 } 572 provider.uninstallCertsAndKeys(); 573 String packageName = provider.getPackageName(); 574 // Remove any configs corresponding to the profile in WifiConfigManager. 575 mWifiConfigManager.removePasspointConfiguredNetwork( 576 provider.getWifiConfig().getProfileKey()); 577 String uniqueId = provider.getConfig().getUniqueId(); 578 mProviders.remove(uniqueId); 579 mWifiConfigManager.removeConnectChoiceFromAllNetworks(uniqueId); 580 mWifiConfigManager.saveToStore(true /* forceWrite */); 581 582 // Stop monitoring the package if there is no Passpoint profile installed by the package 583 if (mAppOpsChangedListenerPerApp.containsKey(packageName) 584 && getPasspointProviderWithPackage(packageName).size() == 0) { 585 stopTrackingAppOpsChange(packageName); 586 } 587 Log.d(TAG, "Removed Passpoint configuration: " + uniqueId); 588 mWifiMetrics.incrementNumPasspointProviderUninstallSuccess(); 589 return true; 590 } 591 592 /** 593 * Remove a Passpoint provider identified by the given its unique identifier. 594 * 595 * @param callingUid Calling UID. 596 * @param privileged Whether the caller is a privileged entity 597 * @param uniqueId The ID of the provider to remove. Not required if FQDN is specified. 598 * @param fqdn The FQDN of the provider to remove. Not required if unique ID is specified. 599 * @return true if a provider is removed, false otherwise 600 */ removeProvider(int callingUid, boolean privileged, String uniqueId, String fqdn)601 public boolean removeProvider(int callingUid, boolean privileged, String uniqueId, 602 String fqdn) { 603 if (uniqueId == null && fqdn == null) { 604 mWifiMetrics.incrementNumPasspointProviderUninstallation(); 605 Log.e(TAG, "Cannot remove provider, both FQDN and unique ID are null"); 606 return false; 607 } 608 609 if (uniqueId != null) { 610 // Unique identifier provided 611 mWifiMetrics.incrementNumPasspointProviderUninstallation(); 612 PasspointProvider provider = mProviders.get(uniqueId); 613 if (provider == null) { 614 Log.e(TAG, "Config doesn't exist"); 615 return false; 616 } 617 return removeProviderInternal(provider, callingUid, privileged); 618 } 619 620 // FQDN provided, loop through all profiles with matching FQDN 621 ArrayList<PasspointProvider> passpointProviders = new ArrayList<>(mProviders.values()); 622 int removedProviders = 0; 623 int numOfUninstallations = 0; 624 for (PasspointProvider provider : passpointProviders) { 625 if (!TextUtils.equals(provider.getConfig().getHomeSp().getFqdn(), fqdn)) { 626 continue; 627 } 628 mWifiMetrics.incrementNumPasspointProviderUninstallation(); 629 numOfUninstallations++; 630 if (removeProviderInternal(provider, callingUid, privileged)) { 631 removedProviders++; 632 } 633 } 634 635 if (numOfUninstallations == 0) { 636 // Update uninstallation requests metrics here to cover the corner case of trying to 637 // uninstall a non-existent provider. 638 mWifiMetrics.incrementNumPasspointProviderUninstallation(); 639 } 640 641 return removedProviders > 0; 642 } 643 644 /** 645 * Enable or disable the auto-join configuration. Auto-join controls whether or not the 646 * passpoint configuration is used for auto connection (network selection). Note that even 647 * when auto-join is disabled the configuration can still be used for manual connection. 648 * 649 * @param uniqueId The unique identifier of the configuration. Not required if FQDN is specified 650 * @param fqdn The FQDN of the configuration. Not required if uniqueId is specified. 651 * @param enableAutojoin true to enable auto-join, false to disable. 652 * @return true on success, false otherwise (e.g. if no such provider exists). 653 */ enableAutojoin(String uniqueId, String fqdn, boolean enableAutojoin)654 public boolean enableAutojoin(String uniqueId, String fqdn, boolean enableAutojoin) { 655 if (uniqueId == null && fqdn == null) { 656 return false; 657 } 658 if (uniqueId != null) { 659 // Unique identifier provided 660 PasspointProvider provider = mProviders.get(uniqueId); 661 if (provider == null) { 662 Log.e(TAG, "Config doesn't exist"); 663 return false; 664 } 665 if (provider.setAutojoinEnabled(enableAutojoin)) { 666 mWifiMetrics.logUserActionEvent(enableAutojoin 667 ? UserActionEvent.EVENT_CONFIGURE_AUTO_CONNECT_ON 668 : UserActionEvent.EVENT_CONFIGURE_AUTO_CONNECT_OFF, 669 provider.isFromSuggestion(), true); 670 // Update WifiConfigManager if changed. 671 updateWifiConfigInWcmIfPresent(provider.getWifiConfig(), provider.getCreatorUid(), 672 provider.getPackageName(), provider.isFromSuggestion()); 673 } 674 675 mWifiConfigManager.saveToStore(true); 676 return true; 677 } 678 679 ArrayList<PasspointProvider> passpointProviders = new ArrayList<>(mProviders.values()); 680 boolean found = false; 681 682 // FQDN provided, loop through all profiles with matching FQDN 683 for (PasspointProvider provider : passpointProviders) { 684 if (TextUtils.equals(provider.getConfig().getHomeSp().getFqdn(), fqdn)) { 685 if (provider.setAutojoinEnabled(enableAutojoin)) { 686 mWifiMetrics.logUserActionEvent(enableAutojoin 687 ? UserActionEvent.EVENT_CONFIGURE_AUTO_CONNECT_ON 688 : UserActionEvent.EVENT_CONFIGURE_AUTO_CONNECT_OFF, 689 provider.isFromSuggestion(), true); 690 // Update WifiConfigManager if changed. 691 updateWifiConfigInWcmIfPresent(provider.getWifiConfig(), 692 provider.getCreatorUid(), provider.getPackageName(), 693 provider.isFromSuggestion()); 694 } 695 found = true; 696 } 697 } 698 if (found) { 699 mWifiConfigManager.saveToStore(true); 700 } 701 return found; 702 } 703 704 /** 705 * Enable or disable MAC randomization for this passpoint profile. 706 * @param fqdn The FQDN of the configuration 707 * @param enable true to enable MAC randomization, false to disable 708 * @return true on success, false otherwise (e.g. if no such provider exists). 709 */ enableMacRandomization(@onNull String fqdn, boolean enable)710 public boolean enableMacRandomization(@NonNull String fqdn, boolean enable) { 711 ArrayList<PasspointProvider> passpointProviders = new ArrayList<>(mProviders.values()); 712 boolean found = false; 713 714 // Loop through all profiles with matching FQDN 715 for (PasspointProvider provider : passpointProviders) { 716 if (TextUtils.equals(provider.getConfig().getHomeSp().getFqdn(), fqdn)) { 717 boolean settingChanged = provider.setMacRandomizationEnabled(enable); 718 if (settingChanged) { 719 mWifiMetrics.logUserActionEvent(enable 720 ? UserActionEvent.EVENT_CONFIGURE_MAC_RANDOMIZATION_ON 721 : UserActionEvent.EVENT_CONFIGURE_MAC_RANDOMIZATION_OFF, 722 provider.isFromSuggestion(), true); 723 mWifiConfigManager.removePasspointConfiguredNetwork( 724 provider.getWifiConfig().getProfileKey()); 725 } 726 found = true; 727 } 728 } 729 if (found) { 730 mWifiConfigManager.saveToStore(true); 731 } 732 return found; 733 } 734 735 /** 736 * Set the metered override value for this passpoint profile 737 * @param fqdn The FQDN of the configuration 738 * @param meteredOverride One of the values in {@link MeteredOverride} 739 * @return true on success, false otherwise (e.g. if no such provider exists). 740 */ setMeteredOverride(@onNull String fqdn, @MeteredOverride int meteredOverride)741 public boolean setMeteredOverride(@NonNull String fqdn, @MeteredOverride int meteredOverride) { 742 ArrayList<PasspointProvider> passpointProviders = new ArrayList<>(mProviders.values()); 743 boolean found = false; 744 745 // Loop through all profiles with matching FQDN 746 for (PasspointProvider provider : passpointProviders) { 747 if (TextUtils.equals(provider.getConfig().getHomeSp().getFqdn(), fqdn)) { 748 if (provider.setMeteredOverride(meteredOverride)) { 749 mWifiMetrics.logUserActionEvent( 750 WifiMetrics.convertMeteredOverrideEnumToUserActionEventType( 751 meteredOverride), 752 provider.isFromSuggestion(), true); 753 } 754 found = true; 755 } 756 } 757 if (found) { 758 mWifiConfigManager.saveToStore(true); 759 } 760 return found; 761 } 762 763 /** 764 * Return the installed Passpoint provider configurations. 765 * An empty list will be returned when no provider is installed. 766 * 767 * @param callingUid Calling UID. 768 * @param privileged Whether the caller is a privileged entity 769 * @return A list of {@link PasspointConfiguration} 770 */ getProviderConfigs(int callingUid, boolean privileged)771 public List<PasspointConfiguration> getProviderConfigs(int callingUid, 772 boolean privileged) { 773 List<PasspointConfiguration> configs = new ArrayList<>(); 774 for (Map.Entry<String, PasspointProvider> entry : mProviders.entrySet()) { 775 PasspointProvider provider = entry.getValue(); 776 if (privileged || callingUid == provider.getCreatorUid()) { 777 if (provider.isFromSuggestion()) { 778 continue; 779 } 780 configs.add(provider.getConfig()); 781 } 782 } 783 return configs; 784 } 785 786 /** 787 * Find all providers that can provide service through the given AP, which means the 788 * providers contained credential to authenticate with the given AP. 789 * 790 * If there is any home provider available, will return a list of matched home providers. 791 * Otherwise will return a list of matched roaming providers. 792 * 793 * A empty list will be returned if no matching is found. 794 * 795 * @param scanResult The scan result associated with the AP 796 * @return a list of pairs of {@link PasspointProvider} and match status. 797 */ matchProvider( ScanResult scanResult)798 public @NonNull List<Pair<PasspointProvider, PasspointMatch>> matchProvider( 799 ScanResult scanResult) { 800 return matchProvider(scanResult, true); 801 } 802 803 /** 804 * Find all providers that can provide service through the given AP, which means the 805 * providers contained credential to authenticate with the given AP. 806 * 807 * If there is any home provider available, will return a list of matched home providers. 808 * Otherwise will return a list of matched roaming providers. 809 * 810 * A empty list will be returned if no matching is found. 811 * 812 * @param scanResult The scan result associated with the AP 813 * @param anqpRequestAllowed Indicates if to allow ANQP request if the provider's entry is empty 814 * @return a list of pairs of {@link PasspointProvider} and match status. 815 */ matchProvider( ScanResult scanResult, boolean anqpRequestAllowed)816 public @NonNull List<Pair<PasspointProvider, PasspointMatch>> matchProvider( 817 ScanResult scanResult, boolean anqpRequestAllowed) { 818 List<Pair<PasspointProvider, PasspointMatch>> allMatches = getAllMatchedProviders( 819 scanResult, anqpRequestAllowed); 820 if (allMatches.isEmpty()) { 821 return allMatches; 822 } 823 List<Pair<PasspointProvider, PasspointMatch>> homeProviders = new ArrayList<>(); 824 List<Pair<PasspointProvider, PasspointMatch>> roamingProviders = new ArrayList<>(); 825 for (Pair<PasspointProvider, PasspointMatch> match : allMatches) { 826 if (isExpired(match.first.getConfig())) { 827 continue; 828 } 829 if (match.second == PasspointMatch.HomeProvider) { 830 homeProviders.add(match); 831 } else { 832 roamingProviders.add(match); 833 } 834 } 835 836 if (!homeProviders.isEmpty()) { 837 Log.d(TAG, String.format("Matched %s to %s providers as %s", scanResult.SSID, 838 homeProviders.size(), "Home Provider")); 839 return homeProviders; 840 } 841 842 if (!roamingProviders.isEmpty()) { 843 Log.d(TAG, String.format("Matched %s to %s providers as %s", scanResult.SSID, 844 roamingProviders.size(), "Roaming Provider")); 845 return roamingProviders; 846 } 847 848 if (mVerboseLoggingEnabled) { 849 Log.d(TAG, "No service provider found for " + scanResult.SSID); 850 } 851 return new ArrayList<>(); 852 } 853 854 /** 855 * Return a list of all providers that can provide service through the given AP. 856 * 857 * @param scanResult The scan result associated with the AP 858 * @return a list of pairs of {@link PasspointProvider} and match status. 859 */ getAllMatchedProviders( ScanResult scanResult)860 public @NonNull List<Pair<PasspointProvider, PasspointMatch>> getAllMatchedProviders( 861 ScanResult scanResult) { 862 return getAllMatchedProviders(scanResult, true); 863 } 864 865 /** 866 * Return a list of all providers that can provide service through the given AP. 867 * 868 * @param scanResult The scan result associated with the AP 869 * @param anqpRequestAllowed Indicates if to allow ANQP request if the provider's entry is empty 870 * @return a list of pairs of {@link PasspointProvider} and match status. 871 */ getAllMatchedProviders( ScanResult scanResult, boolean anqpRequestAllowed)872 private @NonNull List<Pair<PasspointProvider, PasspointMatch>> getAllMatchedProviders( 873 ScanResult scanResult, boolean anqpRequestAllowed) { 874 List<Pair<PasspointProvider, PasspointMatch>> allMatches = new ArrayList<>(); 875 876 // Retrieve the relevant information elements, mainly Roaming Consortium IE and Hotspot 2.0 877 // Vendor Specific IE. 878 InformationElementUtil.RoamingConsortium roamingConsortium = 879 InformationElementUtil.getRoamingConsortiumIE(scanResult.informationElements); 880 InformationElementUtil.Vsa vsa = InformationElementUtil.getHS2VendorSpecificIE( 881 scanResult.informationElements); 882 883 // Lookup ANQP data in the cache. 884 long bssid; 885 try { 886 bssid = Utils.parseMac(scanResult.BSSID); 887 } catch (IllegalArgumentException e) { 888 Log.e(TAG, "Invalid BSSID provided in the scan result: " + scanResult.BSSID); 889 return allMatches; 890 } 891 ANQPNetworkKey anqpKey = ANQPNetworkKey.buildKey(scanResult.SSID, bssid, scanResult.hessid, 892 vsa.anqpDomainID); 893 ANQPData anqpEntry = mAnqpCache.getEntry(anqpKey); 894 if (anqpEntry == null) { 895 if (anqpRequestAllowed) { 896 mAnqpRequestManager.requestANQPElements(bssid, anqpKey, 897 roamingConsortium.anqpOICount > 0, vsa.hsRelease); 898 } 899 Log.d(TAG, "ANQP entry not found for: " + anqpKey); 900 return allMatches; 901 } 902 boolean anyProviderUpdated = false; 903 for (Map.Entry<String, PasspointProvider> entry : mProviders.entrySet()) { 904 PasspointProvider provider = entry.getValue(); 905 if (provider.tryUpdateCarrierId()) { 906 anyProviderUpdated = true; 907 } 908 if (mVerboseLoggingEnabled) { 909 Log.d(TAG, "Matching provider " + provider.getConfig().getHomeSp().getFqdn() 910 + " with " 911 + anqpEntry.getElements().get(Constants.ANQPElementType.ANQPDomName)); 912 } 913 PasspointMatch matchStatus = provider.match(anqpEntry.getElements(), 914 roamingConsortium, scanResult); 915 if (matchStatus == PasspointMatch.HomeProvider 916 || matchStatus == PasspointMatch.RoamingProvider) { 917 allMatches.add(Pair.create(provider, matchStatus)); 918 } 919 } 920 if (anyProviderUpdated) { 921 mWifiConfigManager.saveToStore(true); 922 } 923 if (allMatches.size() != 0) { 924 for (Pair<PasspointProvider, PasspointMatch> match : allMatches) { 925 Log.d(TAG, String.format("Matched %s to %s as %s", scanResult.SSID, 926 match.first.getConfig().getHomeSp().getFqdn(), 927 match.second == PasspointMatch.HomeProvider ? "Home Provider" 928 : "Roaming Provider")); 929 } 930 } else { 931 if (mVerboseLoggingEnabled) { 932 Log.d(TAG, "No service providers found for " + scanResult.SSID); 933 } 934 } 935 return allMatches; 936 } 937 938 /** 939 * Add a legacy Passpoint configuration represented by a {@link WifiConfiguration} to the 940 * current {@link PasspointManager}. 941 * 942 * This will not trigger a config store write, since this will be invoked as part of the 943 * configuration migration, the caller will be responsible for triggering store write 944 * after the migration is completed. 945 * 946 * @param config {@link WifiConfiguration} representation of the Passpoint configuration 947 * @return true on success 948 */ addLegacyPasspointConfig(WifiConfiguration config)949 public static boolean addLegacyPasspointConfig(WifiConfiguration config) { 950 if (sPasspointManager == null) { 951 Log.e(TAG, "PasspointManager have not been initialized yet"); 952 return false; 953 } 954 Log.d(TAG, "Installing legacy Passpoint configuration: " + config.FQDN); 955 return sPasspointManager.addWifiConfig(config); 956 } 957 958 /** 959 * Sweep the ANQP cache to remove expired entries. 960 */ sweepCache()961 public void sweepCache() { 962 mAnqpCache.sweep(); 963 } 964 965 /** 966 * Notify the completion of an ANQP request. 967 * TODO(zqiu): currently the notification is done through WifiMonitor, 968 * will no longer be the case once we switch over to use wificond. 969 */ notifyANQPDone(AnqpEvent anqpEvent)970 public void notifyANQPDone(AnqpEvent anqpEvent) { 971 mPasspointEventHandler.notifyANQPDone(anqpEvent); 972 } 973 974 /** 975 * Notify the completion of an icon request. 976 * TODO(zqiu): currently the notification is done through WifiMonitor, 977 * will no longer be the case once we switch over to use wificond. 978 */ notifyIconDone(IconEvent iconEvent)979 public void notifyIconDone(IconEvent iconEvent) { 980 mPasspointEventHandler.notifyIconDone(iconEvent); 981 } 982 983 /** 984 * Notify the reception of a Wireless Network Management (WNM) frame. 985 */ receivedWnmFrame(WnmData data)986 public void receivedWnmFrame(WnmData data) { 987 mPasspointEventHandler.notifyWnmFrameReceived(data); 988 } 989 990 /** 991 * Request the specified icon file |fileName| from the specified AP |bssid|. 992 * @return true if the request is sent successfully, false otherwise 993 */ queryPasspointIcon(long bssid, String fileName)994 public boolean queryPasspointIcon(long bssid, String fileName) { 995 return mPasspointEventHandler.requestIcon(bssid, fileName); 996 } 997 998 /** 999 * Lookup the ANQP elements associated with the given AP from the cache. An empty map 1000 * will be returned if no match found in the cache. 1001 * 1002 * @param scanResult The scan result associated with the AP 1003 * @return Map of ANQP elements 1004 */ getANQPElements(ScanResult scanResult)1005 public Map<Constants.ANQPElementType, ANQPElement> getANQPElements(ScanResult scanResult) { 1006 // Retrieve the Hotspot 2.0 Vendor Specific IE. 1007 InformationElementUtil.Vsa vsa = 1008 InformationElementUtil.getHS2VendorSpecificIE(scanResult.informationElements); 1009 1010 // Lookup ANQP data in the cache. 1011 long bssid; 1012 try { 1013 bssid = Utils.parseMac(scanResult.BSSID); 1014 } catch (IllegalArgumentException e) { 1015 Log.e(TAG, "Invalid BSSID provided in the scan result: " + scanResult.BSSID); 1016 return new HashMap<>(); 1017 } 1018 ANQPData anqpEntry = mAnqpCache.getEntry(ANQPNetworkKey.buildKey( 1019 scanResult.SSID, bssid, scanResult.hessid, vsa.anqpDomainID)); 1020 if (anqpEntry != null) { 1021 return anqpEntry.getElements(); 1022 } 1023 return new HashMap<>(); 1024 } 1025 1026 /** 1027 * Return a map of all matching configurations keys with corresponding scanResults (or an empty 1028 * map if none). 1029 * 1030 * @param scanResults The list of scan results 1031 * @return Map that consists of identifies and corresponding scanResults per network type 1032 * ({@link WifiManager#PASSPOINT_HOME_NETWORK}, {@link WifiManager#PASSPOINT_ROAMING_NETWORK}). 1033 */ 1034 public Map<String, Map<Integer, List<ScanResult>>> getAllMatchingPasspointProfilesForScanResults(List<ScanResult> scanResults)1035 getAllMatchingPasspointProfilesForScanResults(List<ScanResult> scanResults) { 1036 if (scanResults == null) { 1037 Log.e(TAG, "Attempt to get matching config for a null ScanResults"); 1038 return new HashMap<>(); 1039 } 1040 Map<String, Map<Integer, List<ScanResult>>> configs = new HashMap<>(); 1041 1042 for (ScanResult scanResult : scanResults) { 1043 if (!scanResult.isPasspointNetwork()) continue; 1044 List<Pair<PasspointProvider, PasspointMatch>> matchedProviders = getAllMatchedProviders( 1045 scanResult); 1046 for (Pair<PasspointProvider, PasspointMatch> matchedProvider : matchedProviders) { 1047 WifiConfiguration config = matchedProvider.first.getWifiConfig(); 1048 int type = WifiManager.PASSPOINT_HOME_NETWORK; 1049 if (!config.isHomeProviderNetwork) { 1050 type = WifiManager.PASSPOINT_ROAMING_NETWORK; 1051 } 1052 Map<Integer, List<ScanResult>> scanResultsPerNetworkType = 1053 configs.computeIfAbsent(config.getProfileKey(), 1054 k -> new HashMap<>()); 1055 List<ScanResult> matchingScanResults = scanResultsPerNetworkType.computeIfAbsent( 1056 type, k -> new ArrayList<>()); 1057 matchingScanResults.add(scanResult); 1058 } 1059 } 1060 1061 return configs; 1062 } 1063 1064 /** 1065 * Returns the list of Hotspot 2.0 OSU (Online Sign-Up) providers associated with the given list 1066 * of ScanResult. 1067 * 1068 * An empty map will be returned when an invalid scanResults are provided or no match is found. 1069 * 1070 * @param scanResults a list of ScanResult that has Passpoint APs. 1071 * @return Map that consists of {@link OsuProvider} and a matching list of {@link ScanResult} 1072 */ getMatchingOsuProviders( List<ScanResult> scanResults)1073 public Map<OsuProvider, List<ScanResult>> getMatchingOsuProviders( 1074 List<ScanResult> scanResults) { 1075 if (scanResults == null) { 1076 Log.e(TAG, "Attempt to retrieve OSU providers for a null ScanResult"); 1077 return new HashMap(); 1078 } 1079 1080 Map<OsuProvider, List<ScanResult>> osuProviders = new HashMap<>(); 1081 for (ScanResult scanResult : scanResults) { 1082 if (!scanResult.isPasspointNetwork()) continue; 1083 1084 // Lookup OSU Providers ANQP element. 1085 Map<Constants.ANQPElementType, ANQPElement> anqpElements = getANQPElements(scanResult); 1086 if (!anqpElements.containsKey(Constants.ANQPElementType.HSOSUProviders)) { 1087 continue; 1088 } 1089 HSOsuProvidersElement element = 1090 (HSOsuProvidersElement) anqpElements.get( 1091 Constants.ANQPElementType.HSOSUProviders); 1092 for (OsuProviderInfo info : element.getProviders()) { 1093 // Set null for OSU-SSID in the class because OSU-SSID is a factor for hotspot 1094 // operator rather than service provider, which means it can be different for 1095 // each hotspot operators. 1096 OsuProvider provider = new OsuProvider((WifiSsid) null, info.getFriendlyNames(), 1097 info.getServiceDescription(), info.getServerUri(), 1098 info.getNetworkAccessIdentifier(), info.getMethodList()); 1099 List<ScanResult> matchingScanResults = osuProviders.get(provider); 1100 if (matchingScanResults == null) { 1101 matchingScanResults = new ArrayList<>(); 1102 osuProviders.put(provider, matchingScanResults); 1103 } 1104 matchingScanResults.add(scanResult); 1105 } 1106 } 1107 return osuProviders; 1108 } 1109 1110 /** 1111 * Returns the matching Passpoint configurations for given OSU(Online Sign-Up) providers 1112 * 1113 * An empty map will be returned when an invalid {@code osuProviders} are provided or no match 1114 * is found. 1115 * 1116 * @param osuProviders a list of {@link OsuProvider} 1117 * @return Map that consists of {@link OsuProvider} and matching {@link PasspointConfiguration}. 1118 */ getMatchingPasspointConfigsForOsuProviders( List<OsuProvider> osuProviders)1119 public Map<OsuProvider, PasspointConfiguration> getMatchingPasspointConfigsForOsuProviders( 1120 List<OsuProvider> osuProviders) { 1121 Map<OsuProvider, PasspointConfiguration> matchingPasspointConfigs = new HashMap<>(); 1122 1123 for (OsuProvider osuProvider : osuProviders) { 1124 Map<String, String> friendlyNamesForOsuProvider = osuProvider.getFriendlyNameList(); 1125 if (friendlyNamesForOsuProvider == null) continue; 1126 for (PasspointProvider provider : mProviders.values()) { 1127 PasspointConfiguration passpointConfiguration = provider.getConfig(); 1128 Map<String, String> serviceFriendlyNamesForPpsMo = 1129 passpointConfiguration.getServiceFriendlyNames(); 1130 if (serviceFriendlyNamesForPpsMo == null) continue; 1131 1132 for (Map.Entry<String, String> entry : serviceFriendlyNamesForPpsMo.entrySet()) { 1133 String lang = entry.getKey(); 1134 String friendlyName = entry.getValue(); 1135 if (friendlyName == null) continue; 1136 String osuFriendlyName = friendlyNamesForOsuProvider.get(lang); 1137 if (osuFriendlyName == null) continue; 1138 if (friendlyName.equals(osuFriendlyName)) { 1139 matchingPasspointConfigs.put(osuProvider, passpointConfiguration); 1140 break; 1141 } 1142 } 1143 } 1144 } 1145 return matchingPasspointConfigs; 1146 } 1147 1148 /** 1149 * Returns the corresponding wifi configurations from {@link WifiConfigManager} for given a list 1150 * of Passpoint profile unique identifiers. 1151 * 1152 * Note: Not all matched Passpoint profile's WifiConfiguration will be returned, only the ones 1153 * already be added into the {@link WifiConfigManager} will be returned. As the returns of this 1154 * method is expected to show in Wifi Picker or use with 1155 * {@link WifiManager#connect(int, WifiManager.ActionListener)} API, each WifiConfiguration must 1156 * have a valid network Id. 1157 * 1158 * An empty list will be returned when no match is found. 1159 * 1160 * @param idList a list of unique identifiers 1161 * @return List of {@link WifiConfiguration} converted from {@link PasspointProvider} 1162 */ getWifiConfigsForPasspointProfiles(List<String> idList)1163 public List<WifiConfiguration> getWifiConfigsForPasspointProfiles(List<String> idList) { 1164 if (mProviders.isEmpty()) { 1165 return Collections.emptyList(); 1166 } 1167 List<WifiConfiguration> configs = new ArrayList<>(); 1168 Set<String> uniqueIdSet = new HashSet<>(); 1169 uniqueIdSet.addAll(idList); 1170 for (String uniqueId : uniqueIdSet) { 1171 PasspointProvider provider = mProviders.get(uniqueId); 1172 if (provider == null) { 1173 continue; 1174 } 1175 WifiConfiguration config = provider.getWifiConfig(); 1176 config = mWifiConfigManager.getConfiguredNetwork(config.getProfileKey()); 1177 if (config == null) { 1178 continue; 1179 } 1180 // If the Passpoint configuration is from a suggestion, check if the app shares this 1181 // suggestion with the user. 1182 if (provider.isFromSuggestion() 1183 && !mWifiInjector.getWifiNetworkSuggestionsManager() 1184 .isPasspointSuggestionSharedWithUser(config)) { 1185 continue; 1186 } 1187 if (mWifiConfigManager.shouldUseEnhancedRandomization(config)) { 1188 config.setRandomizedMacAddress(MacAddress.fromString(DEFAULT_MAC_ADDRESS)); 1189 } else { 1190 MacAddress result = mMacAddressUtil.calculatePersistentMac(config.getNetworkKey(), 1191 mMacAddressUtil.obtainMacRandHashFunction(Process.WIFI_UID)); 1192 if (result != null) { 1193 config.setRandomizedMacAddress(result); 1194 } 1195 } 1196 configs.add(config); 1197 } 1198 return configs; 1199 } 1200 1201 /** 1202 * Invoked when a Passpoint network was successfully connected based on the credentials 1203 * provided by the given Passpoint provider 1204 * 1205 * @param uniqueId The unique identifier of the Passpointprofile 1206 */ onPasspointNetworkConnected(String uniqueId)1207 public void onPasspointNetworkConnected(String uniqueId) { 1208 PasspointProvider provider = mProviders.get(uniqueId); 1209 if (provider == null) { 1210 Log.e(TAG, "Passpoint network connected without provider: " + uniqueId); 1211 return; 1212 } 1213 if (!provider.getHasEverConnected()) { 1214 // First successful connection using this provider. 1215 provider.setHasEverConnected(true); 1216 } 1217 } 1218 1219 /** 1220 * Update metrics related to installed Passpoint providers, this includes the number of 1221 * installed providers and the number of those providers that results in a successful network 1222 * connection. 1223 */ updateMetrics()1224 public void updateMetrics() { 1225 int numProviders = mProviders.size(); 1226 int numConnectedProviders = 0; 1227 for (Map.Entry<String, PasspointProvider> entry : mProviders.entrySet()) { 1228 if (entry.getValue().getHasEverConnected()) { 1229 numConnectedProviders++; 1230 } 1231 } 1232 mWifiMetrics.updateSavedPasspointProfilesInfo(mProviders); 1233 mWifiMetrics.updateSavedPasspointProfiles(numProviders, numConnectedProviders); 1234 } 1235 1236 /** 1237 * Dump the current state of PasspointManager to the provided output stream. 1238 * 1239 * @param pw The output stream to write to 1240 */ dump(PrintWriter pw)1241 public void dump(PrintWriter pw) { 1242 pw.println("Dump of PasspointManager"); 1243 pw.println("PasspointManager - Providers Begin ---"); 1244 for (Map.Entry<String, PasspointProvider> entry : mProviders.entrySet()) { 1245 pw.println(entry.getValue()); 1246 } 1247 pw.println("PasspointManager - Providers End ---"); 1248 pw.println("PasspointManager - Next provider ID to be assigned " + mProviderIndex); 1249 mAnqpCache.dump(pw); 1250 mAnqpRequestManager.dump(pw); 1251 } 1252 1253 /** 1254 * Add a legacy Passpoint configuration represented by a {@link WifiConfiguration}. 1255 * 1256 * @param wifiConfig {@link WifiConfiguration} representation of the Passpoint configuration 1257 * @return true on success 1258 */ addWifiConfig(WifiConfiguration wifiConfig)1259 private boolean addWifiConfig(WifiConfiguration wifiConfig) { 1260 if (wifiConfig == null) { 1261 return false; 1262 } 1263 1264 // Convert to PasspointConfiguration 1265 PasspointConfiguration passpointConfig = 1266 PasspointProvider.convertFromWifiConfig(wifiConfig); 1267 if (passpointConfig == null) { 1268 return false; 1269 } 1270 1271 // Setup aliases for enterprise certificates and key. 1272 WifiEnterpriseConfig enterpriseConfig = wifiConfig.enterpriseConfig; 1273 String caCertificateAliasSuffix = enterpriseConfig.getCaCertificateAlias(); 1274 String clientCertAndKeyAliasSuffix = enterpriseConfig.getClientCertificateAlias(); 1275 if (passpointConfig.getCredential().getUserCredential() != null 1276 && TextUtils.isEmpty(caCertificateAliasSuffix)) { 1277 Log.e(TAG, "Missing CA Certificate for user credential"); 1278 return false; 1279 } 1280 if (passpointConfig.getCredential().getCertCredential() != null) { 1281 if (TextUtils.isEmpty(caCertificateAliasSuffix)) { 1282 Log.e(TAG, "Missing CA certificate for Certificate credential"); 1283 return false; 1284 } 1285 if (TextUtils.isEmpty(clientCertAndKeyAliasSuffix)) { 1286 Log.e(TAG, "Missing client certificate and key for certificate credential"); 1287 return false; 1288 } 1289 } 1290 1291 // Note that for legacy configuration, the alias for client private key is the same as the 1292 // alias for the client certificate. 1293 PasspointProvider provider = new PasspointProvider(passpointConfig, mKeyStore, 1294 mWifiCarrierInfoManager, 1295 mProviderIndex++, wifiConfig.creatorUid, null, false, 1296 Arrays.asList(enterpriseConfig.getCaCertificateAlias()), 1297 enterpriseConfig.getClientCertificateAlias(), null, false, false, mClock); 1298 provider.enableVerboseLogging(mVerboseLoggingEnabled); 1299 mProviders.put(passpointConfig.getUniqueId(), provider); 1300 return true; 1301 } 1302 1303 /** 1304 * Start the subscription provisioning flow with a provider. 1305 * @param callingUid integer indicating the uid of the caller 1306 * @param provider {@link OsuProvider} the provider to subscribe to 1307 * @param callback {@link IProvisioningCallback} callback to update status to the caller 1308 * @return boolean return value from the provisioning method 1309 */ startSubscriptionProvisioning(int callingUid, OsuProvider provider, IProvisioningCallback callback)1310 public boolean startSubscriptionProvisioning(int callingUid, OsuProvider provider, 1311 IProvisioningCallback callback) { 1312 return mPasspointProvisioner.startSubscriptionProvisioning(callingUid, provider, callback); 1313 } 1314 1315 /** 1316 * Check if a Passpoint configuration is expired 1317 * 1318 * @param config {@link PasspointConfiguration} Passpoint configuration 1319 * @return True if the configuration is expired, false if not or expiration is unset 1320 */ isExpired(@onNull PasspointConfiguration config)1321 private boolean isExpired(@NonNull PasspointConfiguration config) { 1322 long expirationTime = config.getSubscriptionExpirationTimeMillis(); 1323 1324 if (expirationTime != Long.MIN_VALUE) { 1325 long curTime = System.currentTimeMillis(); 1326 1327 // Check expiration and return true for expired profiles 1328 if (curTime >= expirationTime) { 1329 Log.d(TAG, "Profile for " + config.getServiceFriendlyName() + " has expired, " 1330 + "expiration time: " + expirationTime + ", current time: " 1331 + curTime); 1332 return true; 1333 } 1334 } 1335 return false; 1336 } 1337 1338 /** 1339 * Get the filtered ScanResults which could be served by the {@link PasspointConfiguration}. 1340 * @param passpointConfiguration The instance of {@link PasspointConfiguration} 1341 * @param scanResults The list of {@link ScanResult} 1342 * @return The filtered ScanResults 1343 */ 1344 @NonNull getMatchingScanResults( @onNull PasspointConfiguration passpointConfiguration, @NonNull List<ScanResult> scanResults)1345 public List<ScanResult> getMatchingScanResults( 1346 @NonNull PasspointConfiguration passpointConfiguration, 1347 @NonNull List<ScanResult> scanResults) { 1348 PasspointProvider provider = mObjectFactory.makePasspointProvider(passpointConfiguration, 1349 null, mWifiCarrierInfoManager, 0, 0, null, false, mClock); 1350 List<ScanResult> filteredScanResults = new ArrayList<>(); 1351 for (ScanResult scanResult : scanResults) { 1352 PasspointMatch matchInfo = provider.match(getANQPElements(scanResult), 1353 InformationElementUtil.getRoamingConsortiumIE(scanResult.informationElements), 1354 scanResult); 1355 if (matchInfo == PasspointMatch.HomeProvider 1356 || matchInfo == PasspointMatch.RoamingProvider) { 1357 filteredScanResults.add(scanResult); 1358 } 1359 } 1360 1361 return filteredScanResults; 1362 } 1363 1364 /** 1365 * Check if the providers list is empty 1366 * 1367 * @return true if the providers list is empty, false otherwise 1368 */ isProvidersListEmpty()1369 public boolean isProvidersListEmpty() { 1370 return mProviders.isEmpty(); 1371 } 1372 1373 /** 1374 * Clear ANQP requests and flush ANQP Cache (for factory reset) 1375 */ clearAnqpRequestsAndFlushCache()1376 public void clearAnqpRequestsAndFlushCache() { 1377 mAnqpRequestManager.clear(); 1378 mAnqpCache.flush(); 1379 mProviders.values().stream().forEach(p -> p.clearProviderBlock()); 1380 } 1381 1382 private PKIXParameters mInjectedPKIXParameters; 1383 private boolean mUseInjectedPKIX = false; 1384 1385 1386 /** 1387 * Used to speedup unit test. 1388 */ 1389 @VisibleForTesting injectPKIXParameters(PKIXParameters params)1390 public void injectPKIXParameters(PKIXParameters params) { 1391 mInjectedPKIXParameters = params; 1392 } 1393 1394 /** 1395 * Used to speedup unit test. 1396 */ 1397 @VisibleForTesting setUseInjectedPKIX(boolean value)1398 public void setUseInjectedPKIX(boolean value) { 1399 mUseInjectedPKIX = value; 1400 } 1401 1402 /** 1403 * Verify that the given certificate is trusted by one of the pre-loaded public CAs in the 1404 * system key store. 1405 * 1406 * @param caCert The CA Certificate to verify 1407 * @throws CertPathValidatorException 1408 * @throws Exception 1409 */ verifyCaCert(X509Certificate caCert)1410 private void verifyCaCert(X509Certificate caCert) 1411 throws GeneralSecurityException, IOException { 1412 CertificateFactory factory = CertificateFactory.getInstance("X.509"); 1413 CertPathValidator validator = 1414 CertPathValidator.getInstance(CertPathValidator.getDefaultType()); 1415 CertPath path = factory.generateCertPath(Arrays.asList(caCert)); 1416 PKIXParameters params; 1417 if (mUseInjectedPKIX) { 1418 params = mInjectedPKIXParameters; 1419 } else { 1420 KeyStore ks = KeyStore.getInstance("AndroidCAStore"); 1421 ks.load(null, null); 1422 params = new PKIXParameters(ks); 1423 params.setRevocationEnabled(false); 1424 } 1425 validator.validate(path, params); 1426 } 1427 1428 /** 1429 * Request the Venue URL ANQP-element from the AP post connection 1430 * 1431 * @param scanResult Scan result associated to the requested AP 1432 */ requestVenueUrlAnqpElement(@onNull ScanResult scanResult)1433 public void requestVenueUrlAnqpElement(@NonNull ScanResult scanResult) { 1434 long bssid; 1435 try { 1436 bssid = Utils.parseMac(scanResult.BSSID); 1437 } catch (IllegalArgumentException e) { 1438 Log.e(TAG, "Invalid BSSID provided in the scan result: " + scanResult.BSSID); 1439 return; 1440 } 1441 InformationElementUtil.Vsa vsa = InformationElementUtil.getHS2VendorSpecificIE( 1442 scanResult.informationElements); 1443 ANQPNetworkKey anqpKey = ANQPNetworkKey.buildKey(scanResult.SSID, bssid, scanResult.hessid, 1444 vsa.anqpDomainID); 1445 // TODO(haishalom@): Should we limit to R3 only? vsa.hsRelease > NetworkDetail.HSRelease.R2 1446 // I am seeing R2's that respond to Venue URL request, so may keep it this way. 1447 // APs that do not support this ANQP request simply ignore it. 1448 mAnqpRequestManager.requestVenueUrlAnqpElement(bssid, anqpKey); 1449 } 1450 1451 /** 1452 * Get the Venue URL associated to the scan result, matched to the system language. If no 1453 * Venue URL matches the system language, then entry number one is returned, which is considered 1454 * to be the venue's default language. 1455 * 1456 * @param scanResult Scan result 1457 * @return The Venue URL associated to the scan result or null if not found 1458 */ 1459 @Nullable getVenueUrl(@onNull ScanResult scanResult)1460 public URL getVenueUrl(@NonNull ScanResult scanResult) { 1461 long bssid; 1462 try { 1463 bssid = Utils.parseMac(scanResult.BSSID); 1464 } catch (IllegalArgumentException e) { 1465 Log.e(TAG, "Invalid BSSID provided in the scan result: " + scanResult.BSSID); 1466 return null; 1467 } 1468 InformationElementUtil.Vsa vsa = InformationElementUtil.getHS2VendorSpecificIE( 1469 scanResult.informationElements); 1470 ANQPNetworkKey anqpKey = ANQPNetworkKey.buildKey(scanResult.SSID, bssid, scanResult.hessid, 1471 vsa.anqpDomainID); 1472 ANQPData anqpEntry = mAnqpCache.getEntry(anqpKey); 1473 if (anqpEntry == null) { 1474 return null; 1475 } 1476 VenueUrlElement venueUrlElement = (VenueUrlElement) 1477 anqpEntry.getElements().get(Constants.ANQPElementType.ANQPVenueUrl); 1478 if (venueUrlElement == null || venueUrlElement.getVenueUrls().isEmpty()) { 1479 return null; // No Venue URL 1480 } 1481 VenueNameElement venueNameElement = (VenueNameElement) 1482 anqpEntry.getElements().get(Constants.ANQPElementType.ANQPVenueName); 1483 if (venueNameElement == null 1484 || venueUrlElement.getVenueUrls().size() != venueNameElement.getNames().size()) { 1485 Log.w(TAG, "Venue name list size mismatches the Venue URL list size"); 1486 return null; // No match between Venue names Venue URLs 1487 } 1488 1489 // Find the Venue URL that matches the system language. Venue URLs are ordered by venue 1490 // names. 1491 Locale locale = Locale.getDefault(); 1492 URL venueUrl = null; 1493 int index = 1; 1494 for (I18Name venueName : venueNameElement.getNames()) { 1495 if (venueName.getLanguage().equals(locale.getISO3Language())) { 1496 venueUrl = venueUrlElement.getVenueUrls().get(index); 1497 break; 1498 } 1499 index++; 1500 } 1501 1502 // If no venue URL for the system language is available, use entry number one 1503 if (venueUrl == null) { 1504 venueUrl = venueUrlElement.getVenueUrls().get(1); 1505 } 1506 1507 if (mVerboseLoggingEnabled) { 1508 Log.d(TAG, "Venue URL to display (language = " + locale.getDisplayLanguage() 1509 + "): " + (venueUrl != null ? venueUrl : "None")); 1510 } 1511 return venueUrl; 1512 } 1513 1514 /** 1515 * Handle Deauthentication Imminent WNM-Notification event 1516 * 1517 * @param event Deauthentication Imminent WNM-Notification data 1518 * @param config Configuration of the currently connected network 1519 */ handleDeauthImminentEvent(WnmData event, WifiConfiguration config)1520 public void handleDeauthImminentEvent(WnmData event, WifiConfiguration config) { 1521 if (event == null || config == null) { 1522 return; 1523 } 1524 1525 blockProvider(config.getProfileKey(), event.getBssid(), event.isEss(), 1526 event.getDelay()); 1527 mWifiMetrics.incrementPasspointDeauthImminentScope(event.isEss()); 1528 } 1529 1530 /** 1531 * Block a specific provider from network selection 1532 * 1533 * @param passpointUniqueId The unique ID of the Passpoint network 1534 * @param bssid BSSID of the AP 1535 * @param isEss Block the ESS or the BSS 1536 * @param delay Delay in seconds 1537 */ blockProvider(String passpointUniqueId, long bssid, boolean isEss, int delay)1538 private void blockProvider(String passpointUniqueId, long bssid, boolean isEss, int delay) { 1539 PasspointProvider provider = mProviders.get(passpointUniqueId); 1540 if (provider != null) { 1541 provider.blockBssOrEss(bssid, isEss, delay); 1542 } 1543 } 1544 1545 /** 1546 * Store the AnonymousIdentity for passpoint after connection. 1547 */ setAnonymousIdentity(WifiConfiguration configuration)1548 public void setAnonymousIdentity(WifiConfiguration configuration) { 1549 if (!configuration.isPasspoint()) { 1550 return; 1551 } 1552 PasspointProvider provider = mProviders.get(configuration.getProfileKey()); 1553 if (provider != null) { 1554 provider.setAnonymousIdentity(configuration.enterpriseConfig.getAnonymousIdentity()); 1555 mWifiConfigManager.saveToStore(true); 1556 } 1557 } 1558 1559 /** 1560 * Resets all sim networks state. 1561 */ resetSimPasspointNetwork()1562 public void resetSimPasspointNetwork() { 1563 mProviders.values().stream().forEach(p -> p.setAnonymousIdentity(null)); 1564 mWifiConfigManager.saveToStore(true); 1565 } 1566 1567 /** 1568 * Handle Terms & Conditions acceptance required WNM-Notification event 1569 * 1570 * @param event Terms & Conditions WNM-Notification data 1571 * @param config Configuration of the currently connected Passpoint network 1572 * 1573 * @return The Terms & conditions URL if it is valid, null otherwise 1574 */ handleTermsAndConditionsEvent(WnmData event, WifiConfiguration config)1575 public URL handleTermsAndConditionsEvent(WnmData event, WifiConfiguration config) { 1576 if (event == null || config == null || !config.isPasspoint()) { 1577 return null; 1578 } 1579 final int oneHourInSeconds = 60 * 60; 1580 final int twentyFourHoursInSeconds = 24 * 60 * 60; 1581 final URL termsAndConditionsUrl; 1582 try { 1583 termsAndConditionsUrl = new URL(event.getUrl()); 1584 } catch (java.net.MalformedURLException e) { 1585 Log.e(TAG, "Malformed Terms and Conditions URL: " + event.getUrl() 1586 + " from BSSID: " + Utils.macToString(event.getBssid())); 1587 1588 // Block this provider for an hour, this unlikely issue may be resolved shortly 1589 blockProvider(config.getProfileKey(), event.getBssid(), true, oneHourInSeconds); 1590 return null; 1591 } 1592 // Reject URLs that are not HTTPS 1593 if (!TextUtils.equals(termsAndConditionsUrl.getProtocol(), "https")) { 1594 Log.e(TAG, "Non-HTTPS Terms and Conditions URL rejected: " + termsAndConditionsUrl 1595 + " from BSSID: " + Utils.macToString(event.getBssid())); 1596 1597 // Block this provider for 24 hours, it is unlikely to be changed 1598 blockProvider(config.getProfileKey(), event.getBssid(), true, 1599 twentyFourHoursInSeconds); 1600 return null; 1601 } 1602 Log.i(TAG, "Captive network, Terms and Conditions URL: " + termsAndConditionsUrl 1603 + " from BSSID: " + Utils.macToString(event.getBssid())); 1604 return termsAndConditionsUrl; 1605 } 1606 } 1607