PasspointManager.java revision b48796131ddd016071144f77f208360e8c408f0e
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_ICON_INFO; 26import static android.net.wifi.WifiManager.EXTRA_SUBSCRIPTION_REMEDIATION_METHOD; 27import static android.net.wifi.WifiManager.EXTRA_URL; 28 29import android.content.Context; 30import android.content.Intent; 31import android.net.wifi.IconInfo; 32import android.net.wifi.WifiConfiguration; 33import android.net.wifi.WifiEnterpriseConfig; 34import android.net.wifi.hotspot2.PasspointConfiguration; 35import android.os.UserHandle; 36import android.text.TextUtils; 37import android.util.Log; 38import android.util.Pair; 39 40import com.android.server.wifi.Clock; 41import com.android.server.wifi.SIMAccessor; 42import com.android.server.wifi.ScanDetail; 43import com.android.server.wifi.WifiConfigManager; 44import com.android.server.wifi.WifiConfigStore; 45import com.android.server.wifi.WifiKeyStore; 46import com.android.server.wifi.WifiNative; 47import com.android.server.wifi.hotspot2.anqp.ANQPElement; 48import com.android.server.wifi.hotspot2.anqp.Constants; 49 50import java.util.ArrayList; 51import java.util.HashMap; 52import java.util.List; 53import java.util.Map; 54 55/** 56 * This class provides the APIs to manage Passpoint provider configurations. 57 * It deals with the following: 58 * - Maintaining a list of configured Passpoint providers for provider matching. 59 * - Persisting the providers configurations to store when required. 60 * - matching Passpoint providers based on the scan results 61 * - Supporting WifiManager Public API calls: 62 * > addOrUpdatePasspointConfiguration() 63 * > removePasspointConfiguration() 64 * > getPasspointConfigurations() 65 * 66 * The provider matching requires obtaining additional information from the AP (ANQP elements). 67 * The ANQP elements will be cached using {@link AnqpCache} to avoid unnecessary requests. 68 * 69 * NOTE: These API's are not thread safe and should only be used from WifiStateMachine thread. 70 */ 71public class PasspointManager { 72 private static final String TAG = "PasspointManager"; 73 74 /** 75 * Handle for the current {@link PasspointManager} instance. This is needed to avoid 76 * circular dependency with the WifiConfigManger, it will be used for adding the 77 * legacy Passpoint configurations. 78 * 79 * This can be eliminated once we can remove the dependency for WifiConfigManager (for 80 * triggering config store write) from this class. 81 */ 82 private static PasspointManager sPasspointManager; 83 84 private final PasspointEventHandler mHandler; 85 private final SIMAccessor mSimAccessor; 86 private final WifiKeyStore mKeyStore; 87 private final PasspointObjectFactory mObjectFactory; 88 private final Map<String, PasspointProvider> mProviders; 89 private final AnqpCache mAnqpCache; 90 private final ANQPRequestManager mAnqpRequestManager; 91 private final WifiConfigManager mWifiConfigManager; 92 93 // Counter used for assigning unique identifier to each provider. 94 private long mProviderIndex; 95 96 private class CallbackHandler implements PasspointEventHandler.Callbacks { 97 private final Context mContext; 98 CallbackHandler(Context context) { 99 mContext = context; 100 } 101 102 @Override 103 public void onANQPResponse(long bssid, 104 Map<Constants.ANQPElementType, ANQPElement> anqpElements) { 105 // Notify request manager for the completion of a request. 106 ScanDetail scanDetail = 107 mAnqpRequestManager.onRequestCompleted(bssid, anqpElements != null); 108 if (anqpElements == null || scanDetail == null) { 109 // Query failed or the request wasn't originated from us (not tracked by the 110 // request manager). Nothing to be done. 111 return; 112 } 113 114 // Add new entry to the cache. 115 NetworkDetail networkDetail = scanDetail.getNetworkDetail(); 116 ANQPNetworkKey anqpKey = ANQPNetworkKey.buildKey(networkDetail.getSSID(), 117 networkDetail.getBSSID(), networkDetail.getHESSID(), 118 networkDetail.getAnqpDomainID()); 119 mAnqpCache.addEntry(anqpKey, anqpElements); 120 121 // Update ANQP elements in the ScanDetail. 122 scanDetail.propagateANQPInfo(anqpElements); 123 } 124 125 @Override 126 public void onIconResponse(long bssid, String fileName, byte[] data) { 127 Intent intent = new Intent(ACTION_PASSPOINT_ICON); 128 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); 129 intent.putExtra(EXTRA_BSSID_LONG, bssid); 130 intent.putExtra(EXTRA_ICON_INFO, new IconInfo(fileName, data)); 131 mContext.sendBroadcastAsUser(intent, UserHandle.ALL, 132 android.Manifest.permission.ACCESS_WIFI_STATE); 133 } 134 135 @Override 136 public void onWnmFrameReceived(WnmData event) { 137 // %012x HS20-SUBSCRIPTION-REMEDIATION "%u %s", osu_method, url 138 // %012x HS20-DEAUTH-IMMINENT-NOTICE "%u %u %s", code, reauth_delay, url 139 Intent intent; 140 if (event.isDeauthEvent()) { 141 intent = new Intent(ACTION_PASSPOINT_DEAUTH_IMMINENT); 142 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); 143 intent.putExtra(EXTRA_BSSID_LONG, event.getBssid()); 144 intent.putExtra(EXTRA_URL, event.getUrl()); 145 intent.putExtra(EXTRA_ESS, event.isEss()); 146 intent.putExtra(EXTRA_DELAY, event.getDelay()); 147 } else { 148 intent = new Intent(ACTION_PASSPOINT_SUBSCRIPTION_REMEDIATION); 149 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); 150 intent.putExtra(EXTRA_BSSID_LONG, event.getBssid()); 151 intent.putExtra(EXTRA_SUBSCRIPTION_REMEDIATION_METHOD, event.getMethod()); 152 intent.putExtra(EXTRA_URL, event.getUrl()); 153 } 154 mContext.sendBroadcastAsUser(intent, UserHandle.ALL, 155 android.Manifest.permission.ACCESS_WIFI_STATE); 156 } 157 } 158 159 /** 160 * Data provider for the Passpoint configuration store data {@link PasspointConfigStoreData}. 161 */ 162 private class DataSourceHandler implements PasspointConfigStoreData.DataSource { 163 @Override 164 public List<PasspointProvider> getProviders() { 165 List<PasspointProvider> providers = new ArrayList<>(); 166 for (Map.Entry<String, PasspointProvider> entry : mProviders.entrySet()) { 167 providers.add(entry.getValue()); 168 } 169 return providers; 170 } 171 172 @Override 173 public void setProviders(List<PasspointProvider> providers) { 174 mProviders.clear(); 175 for (PasspointProvider provider : providers) { 176 mProviders.put(provider.getConfig().getHomeSp().getFqdn(), provider); 177 } 178 } 179 180 @Override 181 public long getProviderIndex() { 182 return mProviderIndex; 183 } 184 185 @Override 186 public void setProviderIndex(long providerIndex) { 187 mProviderIndex = providerIndex; 188 } 189 } 190 191 public PasspointManager(Context context, WifiNative wifiNative, WifiKeyStore keyStore, 192 Clock clock, SIMAccessor simAccessor, PasspointObjectFactory objectFactory, 193 WifiConfigManager wifiConfigManager, WifiConfigStore wifiConfigStore) { 194 mHandler = objectFactory.makePasspointEventHandler(wifiNative, 195 new CallbackHandler(context)); 196 mKeyStore = keyStore; 197 mSimAccessor = simAccessor; 198 mObjectFactory = objectFactory; 199 mProviders = new HashMap<>(); 200 mAnqpCache = objectFactory.makeAnqpCache(clock); 201 mAnqpRequestManager = objectFactory.makeANQPRequestManager(mHandler, clock); 202 mWifiConfigManager = wifiConfigManager; 203 mProviderIndex = 0; 204 wifiConfigStore.registerStoreData(objectFactory.makePasspointConfigStoreData( 205 mKeyStore, mSimAccessor, new DataSourceHandler())); 206 sPasspointManager = this; 207 } 208 209 /** 210 * Add or update a Passpoint provider with the given configuration. 211 * 212 * Each provider is uniquely identified by its FQDN (Fully Qualified Domain Name). 213 * In the case when there is an existing configuration with the same FQDN 214 * a provider with the new configuration will replace the existing provider. 215 * 216 * @param config Configuration of the Passpoint provider to be added 217 * @return true if provider is added, false otherwise 218 */ 219 public boolean addOrUpdateProvider(PasspointConfiguration config) { 220 if (config == null) { 221 Log.e(TAG, "Configuration not provided"); 222 return false; 223 } 224 if (!config.validate()) { 225 Log.e(TAG, "Invalid configuration"); 226 return false; 227 } 228 229 // Create a provider and install the necessary certificates and keys. 230 PasspointProvider newProvider = mObjectFactory.makePasspointProvider( 231 config, mKeyStore, mSimAccessor, mProviderIndex++); 232 233 if (!newProvider.installCertsAndKeys()) { 234 Log.e(TAG, "Failed to install certificates and keys to keystore"); 235 return false; 236 } 237 238 // Remove existing provider with the same FQDN. 239 if (mProviders.containsKey(config.getHomeSp().getFqdn())) { 240 Log.d(TAG, "Replacing configuration for " + config.getHomeSp().getFqdn()); 241 mProviders.get(config.getHomeSp().getFqdn()).uninstallCertsAndKeys(); 242 mProviders.remove(config.getHomeSp().getFqdn()); 243 } 244 245 mProviders.put(config.getHomeSp().getFqdn(), newProvider); 246 mWifiConfigManager.saveToStore(true /* forceWrite */); 247 return true; 248 } 249 250 /** 251 * Remove a Passpoint provider identified by the given FQDN. 252 * 253 * @param fqdn The FQDN of the provider to remove 254 * @return true if a provider is removed, false otherwise 255 */ 256 public boolean removeProvider(String fqdn) { 257 if (!mProviders.containsKey(fqdn)) { 258 Log.e(TAG, "Config doesn't exist"); 259 return false; 260 } 261 262 mProviders.get(fqdn).uninstallCertsAndKeys(); 263 mProviders.remove(fqdn); 264 mWifiConfigManager.saveToStore(true /* forceWrite */); 265 return true; 266 } 267 268 /** 269 * Return the installed Passpoint provider configurations. 270 * 271 * An empty list will be returned when no provider is installed. 272 * 273 * @return A list of {@link PasspointConfiguration} 274 */ 275 public List<PasspointConfiguration> getProviderConfigs() { 276 List<PasspointConfiguration> configs = new ArrayList<>(); 277 for (Map.Entry<String, PasspointProvider> entry : mProviders.entrySet()) { 278 configs.add(entry.getValue().getConfig()); 279 } 280 return configs; 281 } 282 283 /** 284 * Find the providers that can provide service through the given AP, which means the 285 * providers contained credential to authenticate with the given AP. 286 * 287 * An empty list will returned in the case when no match is found. 288 * 289 * @param scanDetail The detail information of the AP 290 * @return List of {@link PasspointProvider} 291 */ 292 public List<Pair<PasspointProvider, PasspointMatch>> matchProvider(ScanDetail scanDetail) { 293 // Nothing to be done if no Passpoint provider is installed. 294 if (mProviders.isEmpty()) { 295 return new ArrayList<Pair<PasspointProvider, PasspointMatch>>(); 296 } 297 298 // Lookup ANQP data in the cache. 299 NetworkDetail networkDetail = scanDetail.getNetworkDetail(); 300 ANQPNetworkKey anqpKey = ANQPNetworkKey.buildKey(networkDetail.getSSID(), 301 networkDetail.getBSSID(), networkDetail.getHESSID(), 302 networkDetail.getAnqpDomainID()); 303 ANQPData anqpEntry = mAnqpCache.getEntry(anqpKey); 304 305 if (anqpEntry == null) { 306 mAnqpRequestManager.requestANQPElements(networkDetail.getBSSID(), scanDetail, 307 networkDetail.getAnqpOICount() > 0, 308 networkDetail.getHSRelease() == NetworkDetail.HSRelease.R2); 309 return new ArrayList<Pair<PasspointProvider, PasspointMatch>>(); 310 } 311 312 List<Pair<PasspointProvider, PasspointMatch>> results = new ArrayList<>(); 313 for (Map.Entry<String, PasspointProvider> entry : mProviders.entrySet()) { 314 PasspointProvider provider = entry.getValue(); 315 PasspointMatch matchStatus = provider.match(anqpEntry.getElements()); 316 if (matchStatus == PasspointMatch.HomeProvider 317 || matchStatus == PasspointMatch.RoamingProvider) { 318 results.add(new Pair<PasspointProvider, PasspointMatch>(provider, matchStatus)); 319 } 320 } 321 return results; 322 } 323 324 /** 325 * Add a legacy Passpoint configuration represented by a {@link WifiConfiguration} to the 326 * current {@link PasspointManager}. 327 * 328 * This will not trigger a config store write, since this will be invoked as part of the 329 * configuration migration, the caller will be responsible for triggering store write 330 * after the migration is completed. 331 * 332 * @param config {@link WifiConfiguration} representation of the Passpoint configuration 333 * @return true on success 334 */ 335 public static boolean addLegacyPasspointConfig(WifiConfiguration config) { 336 if (sPasspointManager == null) { 337 Log.e(TAG, "PasspointManager have not been initialized yet"); 338 return false; 339 } 340 return sPasspointManager.addWifiConfig(config); 341 } 342 343 /** 344 * Sweep the ANQP cache to remove expired entries. 345 */ 346 public void sweepCache() { 347 mAnqpCache.sweep(); 348 } 349 350 /** 351 * Notify the completion of an ANQP request. 352 * TODO(zqiu): currently the notification is done through WifiMonitor, 353 * will no longer be the case once we switch over to use wificond. 354 */ 355 public void notifyANQPDone(AnqpEvent anqpEvent) { 356 mHandler.notifyANQPDone(anqpEvent); 357 } 358 359 /** 360 * Notify the completion of an icon request. 361 * TODO(zqiu): currently the notification is done through WifiMonitor, 362 * will no longer be the case once we switch over to use wificond. 363 */ 364 public void notifyIconDone(IconEvent iconEvent) { 365 mHandler.notifyIconDone(iconEvent); 366 } 367 368 /** 369 * Notify the reception of a Wireless Network Management (WNM) frame. 370 * TODO(zqiu): currently the notification is done through WifiMonitor, 371 * will no longer be the case once we switch over to use wificond. 372 */ 373 public void receivedWnmFrame(WnmData data) { 374 mHandler.notifyWnmFrameReceived(data); 375 } 376 377 /** 378 * Request the specified icon file |fileName| from the specified AP |bssid|. 379 * @return true if the request is sent successfully, false otherwise 380 */ 381 public boolean queryPasspointIcon(long bssid, String fileName) { 382 return mHandler.requestIcon(bssid, fileName); 383 } 384 385 /** 386 * Add a legacy Passpoint configuration represented by a {@link WifiConfiguration}. 387 * 388 * @param wifiConfig {@link WifiConfiguration} representation of the Passpoint configuration 389 * @return true on success 390 */ 391 private boolean addWifiConfig(WifiConfiguration wifiConfig) { 392 if (wifiConfig == null) { 393 return false; 394 } 395 396 // Convert to PasspointConfiguration 397 PasspointConfiguration passpointConfig = 398 PasspointProvider.convertFromWifiConfig(wifiConfig); 399 if (passpointConfig == null) { 400 return false; 401 } 402 403 // Setup aliases for enterprise certificates and key. 404 WifiEnterpriseConfig enterpriseConfig = wifiConfig.enterpriseConfig; 405 String caCertificateAliasSuffix = enterpriseConfig.getCaCertificateAlias(); 406 String clientCertAndKeyAliasSuffix = enterpriseConfig.getClientCertificateAlias(); 407 if (passpointConfig.getCredential().getUserCredential() != null 408 && TextUtils.isEmpty(caCertificateAliasSuffix)) { 409 Log.e(TAG, "Missing CA Certificate for user credential"); 410 return false; 411 } 412 if (passpointConfig.getCredential().getCertCredential() != null) { 413 if (TextUtils.isEmpty(caCertificateAliasSuffix)) { 414 Log.e(TAG, "Missing CA certificate for Certificate credential"); 415 return false; 416 } 417 if (TextUtils.isEmpty(clientCertAndKeyAliasSuffix)) { 418 Log.e(TAG, "Missing client certificate and key for certificate credential"); 419 return false; 420 } 421 } 422 423 // Note that for legacy configuration, the alias for client private key is the same as the 424 // alias for the client certificate. 425 PasspointProvider provider = new PasspointProvider(passpointConfig, mKeyStore, 426 mSimAccessor, mProviderIndex++, enterpriseConfig.getCaCertificateAlias(), 427 enterpriseConfig.getClientCertificateAlias(), 428 enterpriseConfig.getClientCertificateAlias()); 429 mProviders.put(passpointConfig.getHomeSp().getFqdn(), provider); 430 return true; 431 } 432} 433