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 17package com.android.server.wifi.hotspot2; 18 19import static android.net.wifi.WifiManager.ACTION_PASSPOINT_DEAUTH_IMMINENT; 20import static android.net.wifi.WifiManager.ACTION_PASSPOINT_ICON; 21import static android.net.wifi.WifiManager.ACTION_PASSPOINT_SUBSCRIPTION_REMEDIATION; 22import static android.net.wifi.WifiManager.EXTRA_BSSID_LONG; 23import static android.net.wifi.WifiManager.EXTRA_DELAY; 24import static android.net.wifi.WifiManager.EXTRA_ESS; 25import static android.net.wifi.WifiManager.EXTRA_FILENAME; 26import static android.net.wifi.WifiManager.EXTRA_ICON; 27import static android.net.wifi.WifiManager.EXTRA_SUBSCRIPTION_REMEDIATION_METHOD; 28import static android.net.wifi.WifiManager.EXTRA_URL; 29 30import android.content.Context; 31import android.content.Intent; 32import android.graphics.drawable.Icon; 33import android.net.wifi.ScanResult; 34import android.net.wifi.WifiConfiguration; 35import android.net.wifi.WifiEnterpriseConfig; 36import android.net.wifi.hotspot2.IProvisioningCallback; 37import android.net.wifi.hotspot2.OsuProvider; 38import android.net.wifi.hotspot2.PasspointConfiguration; 39import android.os.Looper; 40import android.os.UserHandle; 41import android.text.TextUtils; 42import android.util.Log; 43import android.util.Pair; 44 45import com.android.server.wifi.Clock; 46import com.android.server.wifi.SIMAccessor; 47import com.android.server.wifi.WifiConfigManager; 48import com.android.server.wifi.WifiConfigStore; 49import com.android.server.wifi.WifiKeyStore; 50import com.android.server.wifi.WifiMetrics; 51import com.android.server.wifi.WifiNative; 52import com.android.server.wifi.hotspot2.anqp.ANQPElement; 53import com.android.server.wifi.hotspot2.anqp.Constants; 54import com.android.server.wifi.hotspot2.anqp.HSOsuProvidersElement; 55import com.android.server.wifi.hotspot2.anqp.OsuProviderInfo; 56import com.android.server.wifi.util.InformationElementUtil; 57import com.android.server.wifi.util.ScanResultUtil; 58 59import java.io.PrintWriter; 60import java.util.ArrayList; 61import java.util.HashMap; 62import java.util.List; 63import java.util.Map; 64 65/** 66 * This class provides the APIs to manage Passpoint provider configurations. 67 * It deals with the following: 68 * - Maintaining a list of configured Passpoint providers for provider matching. 69 * - Persisting the providers configurations to store when required. 70 * - matching Passpoint providers based on the scan results 71 * - Supporting WifiManager Public API calls: 72 * > addOrUpdatePasspointConfiguration() 73 * > removePasspointConfiguration() 74 * > getPasspointConfigurations() 75 * 76 * The provider matching requires obtaining additional information from the AP (ANQP elements). 77 * The ANQP elements will be cached using {@link AnqpCache} to avoid unnecessary requests. 78 * 79 * NOTE: These API's are not thread safe and should only be used from WifiStateMachine thread. 80 */ 81public class PasspointManager { 82 private static final String TAG = "PasspointManager"; 83 84 /** 85 * Handle for the current {@link PasspointManager} instance. This is needed to avoid 86 * circular dependency with the WifiConfigManger, it will be used for adding the 87 * legacy Passpoint configurations. 88 * 89 * This can be eliminated once we can remove the dependency for WifiConfigManager (for 90 * triggering config store write) from this class. 91 */ 92 private static PasspointManager sPasspointManager; 93 94 private final PasspointEventHandler mHandler; 95 private final SIMAccessor mSimAccessor; 96 private final WifiKeyStore mKeyStore; 97 private final PasspointObjectFactory mObjectFactory; 98 private final Map<String, PasspointProvider> mProviders; 99 private final AnqpCache mAnqpCache; 100 private final ANQPRequestManager mAnqpRequestManager; 101 private final WifiConfigManager mWifiConfigManager; 102 private final CertificateVerifier mCertVerifier; 103 private final WifiMetrics mWifiMetrics; 104 private final PasspointProvisioner mPasspointProvisioner; 105 106 // Counter used for assigning unique identifier to each provider. 107 private long mProviderIndex; 108 109 private class CallbackHandler implements PasspointEventHandler.Callbacks { 110 private final Context mContext; 111 CallbackHandler(Context context) { 112 mContext = context; 113 } 114 115 @Override 116 public void onANQPResponse(long bssid, 117 Map<Constants.ANQPElementType, ANQPElement> anqpElements) { 118 // Notify request manager for the completion of a request. 119 ANQPNetworkKey anqpKey = 120 mAnqpRequestManager.onRequestCompleted(bssid, anqpElements != null); 121 if (anqpElements == null || anqpKey == null) { 122 // Query failed or the request wasn't originated from us (not tracked by the 123 // request manager). Nothing to be done. 124 return; 125 } 126 127 // Add new entry to the cache. 128 mAnqpCache.addEntry(anqpKey, anqpElements); 129 } 130 131 @Override 132 public void onIconResponse(long bssid, String fileName, byte[] data) { 133 Intent intent = new Intent(ACTION_PASSPOINT_ICON); 134 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); 135 intent.putExtra(EXTRA_BSSID_LONG, bssid); 136 intent.putExtra(EXTRA_FILENAME, fileName); 137 if (data != null) { 138 intent.putExtra(EXTRA_ICON, Icon.createWithData(data, 0, data.length)); 139 } 140 mContext.sendBroadcastAsUser(intent, UserHandle.ALL, 141 android.Manifest.permission.ACCESS_WIFI_STATE); 142 } 143 144 @Override 145 public void onWnmFrameReceived(WnmData event) { 146 // %012x HS20-SUBSCRIPTION-REMEDIATION "%u %s", osu_method, url 147 // %012x HS20-DEAUTH-IMMINENT-NOTICE "%u %u %s", code, reauth_delay, url 148 Intent intent; 149 if (event.isDeauthEvent()) { 150 intent = new Intent(ACTION_PASSPOINT_DEAUTH_IMMINENT); 151 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); 152 intent.putExtra(EXTRA_BSSID_LONG, event.getBssid()); 153 intent.putExtra(EXTRA_URL, event.getUrl()); 154 intent.putExtra(EXTRA_ESS, event.isEss()); 155 intent.putExtra(EXTRA_DELAY, event.getDelay()); 156 } else { 157 intent = new Intent(ACTION_PASSPOINT_SUBSCRIPTION_REMEDIATION); 158 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); 159 intent.putExtra(EXTRA_BSSID_LONG, event.getBssid()); 160 intent.putExtra(EXTRA_SUBSCRIPTION_REMEDIATION_METHOD, event.getMethod()); 161 intent.putExtra(EXTRA_URL, event.getUrl()); 162 } 163 mContext.sendBroadcastAsUser(intent, UserHandle.ALL, 164 android.Manifest.permission.ACCESS_WIFI_STATE); 165 } 166 } 167 168 /** 169 * Data provider for the Passpoint configuration store data {@link PasspointConfigStoreData}. 170 */ 171 private class DataSourceHandler implements PasspointConfigStoreData.DataSource { 172 @Override 173 public List<PasspointProvider> getProviders() { 174 List<PasspointProvider> providers = new ArrayList<>(); 175 for (Map.Entry<String, PasspointProvider> entry : mProviders.entrySet()) { 176 providers.add(entry.getValue()); 177 } 178 return providers; 179 } 180 181 @Override 182 public void setProviders(List<PasspointProvider> providers) { 183 mProviders.clear(); 184 for (PasspointProvider provider : providers) { 185 mProviders.put(provider.getConfig().getHomeSp().getFqdn(), provider); 186 } 187 } 188 189 @Override 190 public long getProviderIndex() { 191 return mProviderIndex; 192 } 193 194 @Override 195 public void setProviderIndex(long providerIndex) { 196 mProviderIndex = providerIndex; 197 } 198 } 199 200 public PasspointManager(Context context, WifiNative wifiNative, WifiKeyStore keyStore, 201 Clock clock, SIMAccessor simAccessor, PasspointObjectFactory objectFactory, 202 WifiConfigManager wifiConfigManager, WifiConfigStore wifiConfigStore, 203 WifiMetrics wifiMetrics) { 204 mHandler = objectFactory.makePasspointEventHandler(wifiNative, 205 new CallbackHandler(context)); 206 mKeyStore = keyStore; 207 mSimAccessor = simAccessor; 208 mObjectFactory = objectFactory; 209 mProviders = new HashMap<>(); 210 mAnqpCache = objectFactory.makeAnqpCache(clock); 211 mAnqpRequestManager = objectFactory.makeANQPRequestManager(mHandler, clock); 212 mCertVerifier = objectFactory.makeCertificateVerifier(); 213 mWifiConfigManager = wifiConfigManager; 214 mWifiMetrics = wifiMetrics; 215 mProviderIndex = 0; 216 wifiConfigStore.registerStoreData(objectFactory.makePasspointConfigStoreData( 217 mKeyStore, mSimAccessor, new DataSourceHandler())); 218 mPasspointProvisioner = objectFactory.makePasspointProvisioner(context); 219 sPasspointManager = this; 220 } 221 222 /** 223 * Initializes the provisioning flow with a looper 224 */ 225 public void initializeProvisioner(Looper looper) { 226 mPasspointProvisioner.init(looper); 227 } 228 229 /** 230 * Enable verbose logging 231 * @param verbose more than 0 enables verbose logging 232 */ 233 public void enableVerboseLogging(int verbose) { 234 mPasspointProvisioner.enableVerboseLogging(verbose); 235 } 236 237 /** 238 * Add or update a Passpoint provider with the given configuration. 239 * 240 * Each provider is uniquely identified by its FQDN (Fully Qualified Domain Name). 241 * In the case when there is an existing configuration with the same FQDN 242 * a provider with the new configuration will replace the existing provider. 243 * 244 * @param config Configuration of the Passpoint provider to be added 245 * @return true if provider is added, false otherwise 246 */ 247 public boolean addOrUpdateProvider(PasspointConfiguration config, int uid) { 248 mWifiMetrics.incrementNumPasspointProviderInstallation(); 249 if (config == null) { 250 Log.e(TAG, "Configuration not provided"); 251 return false; 252 } 253 if (!config.validate()) { 254 Log.e(TAG, "Invalid configuration"); 255 return false; 256 } 257 258 // For Hotspot 2.0 Release 1, the CA Certificate must be trusted by one of the pre-loaded 259 // public CAs in the system key store on the device. Since the provisioning method 260 // for Release 1 is not standardized nor trusted, this is a reasonable restriction 261 // to improve security. The presence of UpdateIdentifier is used to differentiate 262 // between R1 and R2 configuration. 263 if (config.getUpdateIdentifier() == Integer.MIN_VALUE 264 && config.getCredential().getCaCertificate() != null) { 265 try { 266 mCertVerifier.verifyCaCert(config.getCredential().getCaCertificate()); 267 } catch (Exception e) { 268 Log.e(TAG, "Failed to verify CA certificate: " + e.getMessage()); 269 return false; 270 } 271 } 272 273 // Create a provider and install the necessary certificates and keys. 274 PasspointProvider newProvider = mObjectFactory.makePasspointProvider( 275 config, mKeyStore, mSimAccessor, mProviderIndex++, uid); 276 277 if (!newProvider.installCertsAndKeys()) { 278 Log.e(TAG, "Failed to install certificates and keys to keystore"); 279 return false; 280 } 281 282 // Remove existing provider with the same FQDN. 283 if (mProviders.containsKey(config.getHomeSp().getFqdn())) { 284 Log.d(TAG, "Replacing configuration for " + config.getHomeSp().getFqdn()); 285 mProviders.get(config.getHomeSp().getFqdn()).uninstallCertsAndKeys(); 286 mProviders.remove(config.getHomeSp().getFqdn()); 287 } 288 289 mProviders.put(config.getHomeSp().getFqdn(), newProvider); 290 mWifiConfigManager.saveToStore(true /* forceWrite */); 291 Log.d(TAG, "Added/updated Passpoint configuration: " + config.getHomeSp().getFqdn() 292 + " by " + uid); 293 mWifiMetrics.incrementNumPasspointProviderInstallSuccess(); 294 return true; 295 } 296 297 /** 298 * Remove a Passpoint provider identified by the given FQDN. 299 * 300 * @param fqdn The FQDN of the provider to remove 301 * @return true if a provider is removed, false otherwise 302 */ 303 public boolean removeProvider(String fqdn) { 304 mWifiMetrics.incrementNumPasspointProviderUninstallation(); 305 if (!mProviders.containsKey(fqdn)) { 306 Log.e(TAG, "Config doesn't exist"); 307 return false; 308 } 309 310 mProviders.get(fqdn).uninstallCertsAndKeys(); 311 mProviders.remove(fqdn); 312 mWifiConfigManager.saveToStore(true /* forceWrite */); 313 Log.d(TAG, "Removed Passpoint configuration: " + fqdn); 314 mWifiMetrics.incrementNumPasspointProviderUninstallSuccess(); 315 return true; 316 } 317 318 /** 319 * Return the installed Passpoint provider configurations. 320 * 321 * An empty list will be returned when no provider is installed. 322 * 323 * @return A list of {@link PasspointConfiguration} 324 */ 325 public List<PasspointConfiguration> getProviderConfigs() { 326 List<PasspointConfiguration> configs = new ArrayList<>(); 327 for (Map.Entry<String, PasspointProvider> entry : mProviders.entrySet()) { 328 configs.add(entry.getValue().getConfig()); 329 } 330 return configs; 331 } 332 333 /** 334 * Find the best provider that can provide service through the given AP, which means the 335 * provider contained credential to authenticate with the given AP. 336 * 337 * Here is the current precedence of the matching rule in descending order: 338 * 1. Home Provider 339 * 2. Roaming Provider 340 * 341 * A {code null} will be returned if no matching is found. 342 * 343 * @param scanResult The scan result associated with the AP 344 * @return A pair of {@link PasspointProvider} and match status. 345 */ 346 public Pair<PasspointProvider, PasspointMatch> matchProvider(ScanResult scanResult) { 347 List<Pair<PasspointProvider, PasspointMatch>> allMatches = getAllMatchedProviders( 348 scanResult); 349 if (allMatches == null) { 350 return null; 351 } 352 353 Pair<PasspointProvider, PasspointMatch> bestMatch = null; 354 for (Pair<PasspointProvider, PasspointMatch> match : allMatches) { 355 if (match.second == PasspointMatch.HomeProvider) { 356 bestMatch = match; 357 break; 358 } 359 if (match.second == PasspointMatch.RoamingProvider && bestMatch == null) { 360 bestMatch = match; 361 } 362 } 363 364 if (bestMatch != null) { 365 Log.d(TAG, String.format("Matched %s to %s as %s", scanResult.SSID, 366 bestMatch.first.getConfig().getHomeSp().getFqdn(), 367 bestMatch.second == PasspointMatch.HomeProvider ? "Home Provider" 368 : "Roaming Provider")); 369 } else { 370 Log.d(TAG, "Match not found for " + scanResult.SSID); 371 } 372 return bestMatch; 373 } 374 375 /** 376 * Return a list of all providers that can provide service through the given AP. 377 * 378 * @param scanResult The scan result associated with the AP 379 * @return a list of pairs of {@link PasspointProvider} and match status. 380 */ 381 public List<Pair<PasspointProvider, PasspointMatch>> getAllMatchedProviders( 382 ScanResult scanResult) { 383 List<Pair<PasspointProvider, PasspointMatch>> allMatches = new ArrayList<>(); 384 385 // Retrieve the relevant information elements, mainly Roaming Consortium IE and Hotspot 2.0 386 // Vendor Specific IE. 387 InformationElementUtil.RoamingConsortium roamingConsortium = 388 InformationElementUtil.getRoamingConsortiumIE(scanResult.informationElements); 389 InformationElementUtil.Vsa vsa = InformationElementUtil.getHS2VendorSpecificIE( 390 scanResult.informationElements); 391 392 // Lookup ANQP data in the cache. 393 long bssid; 394 try { 395 bssid = Utils.parseMac(scanResult.BSSID); 396 } catch (IllegalArgumentException e) { 397 Log.e(TAG, "Invalid BSSID provided in the scan result: " + scanResult.BSSID); 398 return allMatches; 399 } 400 ANQPNetworkKey anqpKey = ANQPNetworkKey.buildKey(scanResult.SSID, bssid, scanResult.hessid, 401 vsa.anqpDomainID); 402 ANQPData anqpEntry = mAnqpCache.getEntry(anqpKey); 403 404 if (anqpEntry == null) { 405 mAnqpRequestManager.requestANQPElements(bssid, anqpKey, 406 roamingConsortium.anqpOICount > 0, 407 vsa.hsRelease == NetworkDetail.HSRelease.R2); 408 Log.d(TAG, "ANQP entry not found for: " + anqpKey); 409 return allMatches; 410 } 411 412 for (Map.Entry<String, PasspointProvider> entry : mProviders.entrySet()) { 413 PasspointProvider provider = entry.getValue(); 414 PasspointMatch matchStatus = provider.match(anqpEntry.getElements(), roamingConsortium); 415 if (matchStatus == PasspointMatch.HomeProvider 416 || matchStatus == PasspointMatch.RoamingProvider) { 417 allMatches.add(Pair.create(provider, matchStatus)); 418 } 419 } 420 421 if (allMatches.size() != 0) { 422 for (Pair<PasspointProvider, PasspointMatch> match : allMatches) { 423 Log.d(TAG, String.format("Matched %s to %s as %s", scanResult.SSID, 424 match.first.getConfig().getHomeSp().getFqdn(), 425 match.second == PasspointMatch.HomeProvider ? "Home Provider" 426 : "Roaming Provider")); 427 } 428 } else { 429 Log.d(TAG, "No matches not found for " + scanResult.SSID); 430 } 431 432 return allMatches; 433 } 434 435 /** 436 * Add a legacy Passpoint configuration represented by a {@link WifiConfiguration} to the 437 * current {@link PasspointManager}. 438 * 439 * This will not trigger a config store write, since this will be invoked as part of the 440 * configuration migration, the caller will be responsible for triggering store write 441 * after the migration is completed. 442 * 443 * @param config {@link WifiConfiguration} representation of the Passpoint configuration 444 * @return true on success 445 */ 446 public static boolean addLegacyPasspointConfig(WifiConfiguration config) { 447 if (sPasspointManager == null) { 448 Log.e(TAG, "PasspointManager have not been initialized yet"); 449 return false; 450 } 451 Log.d(TAG, "Installing legacy Passpoint configuration: " + config.FQDN); 452 return sPasspointManager.addWifiConfig(config); 453 } 454 455 /** 456 * Sweep the ANQP cache to remove expired entries. 457 */ 458 public void sweepCache() { 459 mAnqpCache.sweep(); 460 } 461 462 /** 463 * Notify the completion of an ANQP request. 464 * TODO(zqiu): currently the notification is done through WifiMonitor, 465 * will no longer be the case once we switch over to use wificond. 466 */ 467 public void notifyANQPDone(AnqpEvent anqpEvent) { 468 mHandler.notifyANQPDone(anqpEvent); 469 } 470 471 /** 472 * Notify the completion of an icon request. 473 * TODO(zqiu): currently the notification is done through WifiMonitor, 474 * will no longer be the case once we switch over to use wificond. 475 */ 476 public void notifyIconDone(IconEvent iconEvent) { 477 mHandler.notifyIconDone(iconEvent); 478 } 479 480 /** 481 * Notify the reception of a Wireless Network Management (WNM) frame. 482 * TODO(zqiu): currently the notification is done through WifiMonitor, 483 * will no longer be the case once we switch over to use wificond. 484 */ 485 public void receivedWnmFrame(WnmData data) { 486 mHandler.notifyWnmFrameReceived(data); 487 } 488 489 /** 490 * Request the specified icon file |fileName| from the specified AP |bssid|. 491 * @return true if the request is sent successfully, false otherwise 492 */ 493 public boolean queryPasspointIcon(long bssid, String fileName) { 494 return mHandler.requestIcon(bssid, fileName); 495 } 496 497 /** 498 * Lookup the ANQP elements associated with the given AP from the cache. An empty map 499 * will be returned if no match found in the cache. 500 * 501 * @param scanResult The scan result associated with the AP 502 * @return Map of ANQP elements 503 */ 504 public Map<Constants.ANQPElementType, ANQPElement> getANQPElements(ScanResult scanResult) { 505 // Retrieve the Hotspot 2.0 Vendor Specific IE. 506 InformationElementUtil.Vsa vsa = 507 InformationElementUtil.getHS2VendorSpecificIE(scanResult.informationElements); 508 509 // Lookup ANQP data in the cache. 510 long bssid; 511 try { 512 bssid = Utils.parseMac(scanResult.BSSID); 513 } catch (IllegalArgumentException e) { 514 Log.e(TAG, "Invalid BSSID provided in the scan result: " + scanResult.BSSID); 515 return new HashMap<Constants.ANQPElementType, ANQPElement>(); 516 } 517 ANQPData anqpEntry = mAnqpCache.getEntry(ANQPNetworkKey.buildKey( 518 scanResult.SSID, bssid, scanResult.hessid, vsa.anqpDomainID)); 519 if (anqpEntry != null) { 520 return anqpEntry.getElements(); 521 } 522 return new HashMap<Constants.ANQPElementType, ANQPElement>(); 523 } 524 525 /** 526 * Match the given WiFi AP to an installed Passpoint provider. A {@link WifiConfiguration} 527 * will be generated and returned if a match is found. The returned {@link WifiConfiguration} 528 * will contained all the necessary credentials for connecting to the given WiFi AP. 529 * 530 * A {code null} will be returned if no matching provider is found. 531 * 532 * @param scanResult The scan result of the given AP 533 * @return {@link WifiConfiguration} 534 */ 535 public WifiConfiguration getMatchingWifiConfig(ScanResult scanResult) { 536 if (scanResult == null) { 537 Log.e(TAG, "Attempt to get matching config for a null ScanResult"); 538 return null; 539 } 540 if (!scanResult.isPasspointNetwork()) { 541 Log.e(TAG, "Attempt to get matching config for a non-Passpoint AP"); 542 return null; 543 } 544 Pair<PasspointProvider, PasspointMatch> matchedProvider = matchProvider(scanResult); 545 if (matchedProvider == null) { 546 return null; 547 } 548 WifiConfiguration config = matchedProvider.first.getWifiConfig(); 549 config.SSID = ScanResultUtil.createQuotedSSID(scanResult.SSID); 550 if (matchedProvider.second == PasspointMatch.HomeProvider) { 551 config.isHomeProviderNetwork = true; 552 } 553 return config; 554 } 555 556 /** 557 * Match the given WiFi AP to all installed Passpoint configurations. Return the list of all 558 * matching configurations (or an empty list if none). 559 * 560 * @param scanResult The scan result of the given AP 561 * @return List of {@link WifiConfiguration} 562 */ 563 public List<WifiConfiguration> getAllMatchingWifiConfigs(ScanResult scanResult) { 564 if (scanResult == null) { 565 Log.e(TAG, "Attempt to get matching config for a null ScanResult"); 566 return new ArrayList<WifiConfiguration>(); 567 } 568 if (!scanResult.isPasspointNetwork()) { 569 Log.e(TAG, "Attempt to get matching config for a non-Passpoint AP"); 570 return new ArrayList<WifiConfiguration>(); 571 } 572 573 List<Pair<PasspointProvider, PasspointMatch>> matchedProviders = getAllMatchedProviders( 574 scanResult); 575 List<WifiConfiguration> configs = new ArrayList<>(); 576 for (Pair<PasspointProvider, PasspointMatch> matchedProvider : matchedProviders) { 577 WifiConfiguration config = matchedProvider.first.getWifiConfig(); 578 config.SSID = ScanResultUtil.createQuotedSSID(scanResult.SSID); 579 if (matchedProvider.second == PasspointMatch.HomeProvider) { 580 config.isHomeProviderNetwork = true; 581 } 582 configs.add(config); 583 } 584 585 return configs; 586 } 587 588 /** 589 * Return the list of Hosspot 2.0 OSU (Online Sign-Up) providers associated with the given 590 * AP. 591 * 592 * An empty list will be returned when an invalid scan result is provided or no match is found. 593 * 594 * @param scanResult The scan result of the AP 595 * @return List of {@link OsuProvider} 596 */ 597 public List<OsuProvider> getMatchingOsuProviders(ScanResult scanResult) { 598 if (scanResult == null) { 599 Log.e(TAG, "Attempt to retrieve OSU providers for a null ScanResult"); 600 return new ArrayList<OsuProvider>(); 601 } 602 if (!scanResult.isPasspointNetwork()) { 603 Log.e(TAG, "Attempt to retrieve OSU providers for a non-Passpoint AP"); 604 return new ArrayList<OsuProvider>(); 605 } 606 607 // Lookup OSU Providers ANQP element. 608 Map<Constants.ANQPElementType, ANQPElement> anqpElements = getANQPElements(scanResult); 609 if (!anqpElements.containsKey(Constants.ANQPElementType.HSOSUProviders)) { 610 return new ArrayList<OsuProvider>(); 611 } 612 613 HSOsuProvidersElement element = 614 (HSOsuProvidersElement) anqpElements.get(Constants.ANQPElementType.HSOSUProviders); 615 List<OsuProvider> providers = new ArrayList<>(); 616 for (OsuProviderInfo info : element.getProviders()) { 617 // TODO(b/62256482): include icon data once the icon file retrieval and management 618 // support is added. 619 OsuProvider provider = new OsuProvider(element.getOsuSsid(), info.getFriendlyName(), 620 info.getServiceDescription(), info.getServerUri(), 621 info.getNetworkAccessIdentifier(), info.getMethodList(), null); 622 providers.add(provider); 623 } 624 return providers; 625 } 626 627 /** 628 * Invoked when a Passpoint network was successfully connected based on the credentials 629 * provided by the given Passpoint provider (specified by its FQDN). 630 * 631 * @param fqdn The FQDN of the Passpoint provider 632 */ 633 public void onPasspointNetworkConnected(String fqdn) { 634 PasspointProvider provider = mProviders.get(fqdn); 635 if (provider == null) { 636 Log.e(TAG, "Passpoint network connected without provider: " + fqdn); 637 return; 638 } 639 640 if (!provider.getHasEverConnected()) { 641 // First successful connection using this provider. 642 provider.setHasEverConnected(true); 643 } 644 } 645 646 /** 647 * Update metrics related to installed Passpoint providers, this includes the number of 648 * installed providers and the number of those providers that results in a successful network 649 * connection. 650 */ 651 public void updateMetrics() { 652 int numProviders = mProviders.size(); 653 int numConnectedProviders = 0; 654 for (Map.Entry<String, PasspointProvider> entry : mProviders.entrySet()) { 655 if (entry.getValue().getHasEverConnected()) { 656 numConnectedProviders++; 657 } 658 } 659 mWifiMetrics.updateSavedPasspointProfiles(numProviders, numConnectedProviders); 660 } 661 662 /** 663 * Dump the current state of PasspointManager to the provided output stream. 664 * 665 * @param pw The output stream to write to 666 */ 667 public void dump(PrintWriter pw) { 668 pw.println("Dump of PasspointManager"); 669 pw.println("PasspointManager - Providers Begin ---"); 670 for (Map.Entry<String, PasspointProvider> entry : mProviders.entrySet()) { 671 pw.println(entry.getValue()); 672 } 673 pw.println("PasspointManager - Providers End ---"); 674 pw.println("PasspointManager - Next provider ID to be assigned " + mProviderIndex); 675 mAnqpCache.dump(pw); 676 } 677 678 /** 679 * Add a legacy Passpoint configuration represented by a {@link WifiConfiguration}. 680 * 681 * @param wifiConfig {@link WifiConfiguration} representation of the Passpoint configuration 682 * @return true on success 683 */ 684 private boolean addWifiConfig(WifiConfiguration wifiConfig) { 685 if (wifiConfig == null) { 686 return false; 687 } 688 689 // Convert to PasspointConfiguration 690 PasspointConfiguration passpointConfig = 691 PasspointProvider.convertFromWifiConfig(wifiConfig); 692 if (passpointConfig == null) { 693 return false; 694 } 695 696 // Setup aliases for enterprise certificates and key. 697 WifiEnterpriseConfig enterpriseConfig = wifiConfig.enterpriseConfig; 698 String caCertificateAliasSuffix = enterpriseConfig.getCaCertificateAlias(); 699 String clientCertAndKeyAliasSuffix = enterpriseConfig.getClientCertificateAlias(); 700 if (passpointConfig.getCredential().getUserCredential() != null 701 && TextUtils.isEmpty(caCertificateAliasSuffix)) { 702 Log.e(TAG, "Missing CA Certificate for user credential"); 703 return false; 704 } 705 if (passpointConfig.getCredential().getCertCredential() != null) { 706 if (TextUtils.isEmpty(caCertificateAliasSuffix)) { 707 Log.e(TAG, "Missing CA certificate for Certificate credential"); 708 return false; 709 } 710 if (TextUtils.isEmpty(clientCertAndKeyAliasSuffix)) { 711 Log.e(TAG, "Missing client certificate and key for certificate credential"); 712 return false; 713 } 714 } 715 716 // Note that for legacy configuration, the alias for client private key is the same as the 717 // alias for the client certificate. 718 PasspointProvider provider = new PasspointProvider(passpointConfig, mKeyStore, 719 mSimAccessor, mProviderIndex++, wifiConfig.creatorUid, 720 enterpriseConfig.getCaCertificateAlias(), 721 enterpriseConfig.getClientCertificateAlias(), 722 enterpriseConfig.getClientCertificateAlias(), false, false); 723 mProviders.put(passpointConfig.getHomeSp().getFqdn(), provider); 724 return true; 725 } 726 727 /** 728 * Start the subscription provisioning flow with a provider. 729 * @param callingUid integer indicating the uid of the caller 730 * @param provider {@link OsuProvider} the provider to subscribe to 731 * @param callback {@link IProvisioningCallback} callback to update status to the caller 732 * @return boolean return value from the provisioning method 733 */ 734 public boolean startSubscriptionProvisioning(int callingUid, OsuProvider provider, 735 IProvisioningCallback callback) { 736 return mPasspointProvisioner.startSubscriptionProvisioning(callingUid, provider, callback); 737 } 738} 739